summaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--extmod/modtime.c4
-rw-r--r--extmod/vfs_fat.c8
-rw-r--r--extmod/vfs_lfsx.c8
-rw-r--r--ports/cc3200/mods/modtime.c2
-rw-r--r--ports/esp32/modtime.c2
-rw-r--r--ports/esp8266/modtime.c4
-rw-r--r--ports/mimxrt/modtime.c9
-rw-r--r--ports/renesas-ra/modtime.c2
-rw-r--r--ports/stm32/modtime.c2
-rw-r--r--ports/stm32/mpconfigport.h1
-rw-r--r--ports/unix/Makefile4
-rw-r--r--ports/unix/modtime.c10
-rw-r--r--ports/unix/mpconfigport.h3
-rw-r--r--ports/windows/Makefile4
-rw-r--r--py/mpconfig.h58
-rw-r--r--shared/timeutils/timeutils.c134
-rw-r--r--shared/timeutils/timeutils.h118
-rw-r--r--tests/extmod/time_mktime.py120
18 files changed, 387 insertions, 106 deletions
diff --git a/extmod/modtime.c b/extmod/modtime.c
index deb4bb4c9a..999b81230b 100644
--- a/extmod/modtime.c
+++ b/extmod/modtime.c
@@ -58,7 +58,7 @@ static mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) {
return mp_time_localtime_get();
} else {
// Convert given seconds to tuple.
- mp_int_t seconds = mp_obj_get_int(args[0]);
+ mp_timestamp_t seconds = timeutils_obj_get_timestamp(args[0]);
timeutils_struct_time_t tm;
timeutils_seconds_since_epoch_to_struct_time(seconds, &tm);
mp_obj_t tuple[8] = {
@@ -90,7 +90,7 @@ static mp_obj_t time_mktime(mp_obj_t tuple) {
mp_raise_TypeError(MP_ERROR_TEXT("mktime needs a tuple of length 8 or 9"));
}
- return mp_obj_new_int_from_uint(timeutils_mktime(mp_obj_get_int(elem[0]),
+ return timeutils_obj_from_timestamp(timeutils_mktime(mp_obj_get_int(elem[0]),
mp_obj_get_int(elem[1]), mp_obj_get_int(elem[2]), mp_obj_get_int(elem[3]),
mp_obj_get_int(elem[4]), mp_obj_get_int(elem[5])));
}
diff --git a/extmod/vfs_fat.c b/extmod/vfs_fat.c
index ee1169b8c3..e832992f46 100644
--- a/extmod/vfs_fat.c
+++ b/extmod/vfs_fat.c
@@ -326,7 +326,7 @@ static mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) {
} else {
mode |= MP_S_IFREG;
}
- mp_int_t seconds = timeutils_seconds_since_epoch(
+ mp_timestamp_t seconds = timeutils_seconds_since_epoch(
1980 + ((fno.fdate >> 9) & 0x7f),
(fno.fdate >> 5) & 0x0f,
fno.fdate & 0x1f,
@@ -341,9 +341,9 @@ static mp_obj_t fat_vfs_stat(mp_obj_t vfs_in, mp_obj_t path_in) {
t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
t->items[6] = mp_obj_new_int_from_uint(fno.fsize); // st_size
- t->items[7] = mp_obj_new_int_from_uint(seconds); // st_atime
- t->items[8] = mp_obj_new_int_from_uint(seconds); // st_mtime
- t->items[9] = mp_obj_new_int_from_uint(seconds); // st_ctime
+ t->items[7] = timeutils_obj_from_timestamp(seconds); // st_atime
+ t->items[8] = timeutils_obj_from_timestamp(seconds); // st_mtime
+ t->items[9] = timeutils_obj_from_timestamp(seconds); // st_ctime
return MP_OBJ_FROM_PTR(t);
}
diff --git a/extmod/vfs_lfsx.c b/extmod/vfs_lfsx.c
index 404eab84f4..372037784b 100644
--- a/extmod/vfs_lfsx.c
+++ b/extmod/vfs_lfsx.c
@@ -378,7 +378,7 @@ static mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) {
mp_raise_OSError(-ret);
}
- mp_uint_t mtime = 0;
+ mp_timestamp_t mtime = 0;
#if LFS_BUILD_VERSION == 2
uint8_t mtime_buf[8];
lfs2_ssize_t sz = lfs2_getattr(&self->lfs, path, LFS_ATTR_MTIME, &mtime_buf, sizeof(mtime_buf));
@@ -400,9 +400,9 @@ static mp_obj_t MP_VFS_LFSx(stat)(mp_obj_t self_in, mp_obj_t path_in) {
t->items[4] = MP_OBJ_NEW_SMALL_INT(0); // st_uid
t->items[5] = MP_OBJ_NEW_SMALL_INT(0); // st_gid
t->items[6] = mp_obj_new_int_from_uint(info.size); // st_size
- t->items[7] = mp_obj_new_int_from_uint(mtime); // st_atime
- t->items[8] = mp_obj_new_int_from_uint(mtime); // st_mtime
- t->items[9] = mp_obj_new_int_from_uint(mtime); // st_ctime
+ t->items[7] = timeutils_obj_from_timestamp(mtime); // st_atime
+ t->items[8] = timeutils_obj_from_timestamp(mtime); // st_mtime
+ t->items[9] = timeutils_obj_from_timestamp(mtime); // st_ctime
return MP_OBJ_FROM_PTR(t);
}
diff --git a/ports/cc3200/mods/modtime.c b/ports/cc3200/mods/modtime.c
index 21388568ab..254678fb2d 100644
--- a/ports/cc3200/mods/modtime.c
+++ b/ports/cc3200/mods/modtime.c
@@ -50,5 +50,5 @@ static mp_obj_t mp_time_localtime_get(void) {
// Returns the number of seconds, as an integer, since the Epoch.
static mp_obj_t mp_time_time_get(void) {
- return mp_obj_new_int(pyb_rtc_get_seconds());
+ return timeutils_obj_from_timestamp(pyb_rtc_get_seconds());
}
diff --git a/ports/esp32/modtime.c b/ports/esp32/modtime.c
index 4695dd23e7..991f2cf578 100644
--- a/ports/esp32/modtime.c
+++ b/ports/esp32/modtime.c
@@ -54,5 +54,5 @@ static mp_obj_t mp_time_localtime_get(void) {
static mp_obj_t mp_time_time_get(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
- return mp_obj_new_int(tv.tv_sec);
+ return timeutils_obj_from_timestamp(tv.tv_sec);
}
diff --git a/ports/esp8266/modtime.c b/ports/esp8266/modtime.c
index 0903005597..e99d920fde 100644
--- a/ports/esp8266/modtime.c
+++ b/ports/esp8266/modtime.c
@@ -31,7 +31,7 @@
// Return the localtime as an 8-tuple.
static mp_obj_t mp_time_localtime_get(void) {
- mp_int_t seconds = pyb_rtc_get_us_since_epoch() / 1000 / 1000;
+ mp_uint_t seconds = pyb_rtc_get_us_since_epoch() / 1000u / 1000u;
timeutils_struct_time_t tm;
timeutils_seconds_since_epoch_to_struct_time(seconds, &tm);
mp_obj_t tuple[8] = {
@@ -50,5 +50,5 @@ static mp_obj_t mp_time_localtime_get(void) {
// Returns the number of seconds, as an integer, since the Epoch.
static mp_obj_t mp_time_time_get(void) {
// get date and time
- return mp_obj_new_int(pyb_rtc_get_us_since_epoch() / 1000 / 1000);
+ return timeutils_obj_from_timestamp(pyb_rtc_get_us_since_epoch() / 1000 / 1000);
}
diff --git a/ports/mimxrt/modtime.c b/ports/mimxrt/modtime.c
index a2ccf1b273..3bcfac411c 100644
--- a/ports/mimxrt/modtime.c
+++ b/ports/mimxrt/modtime.c
@@ -51,14 +51,7 @@ static mp_obj_t mp_time_localtime_get(void) {
static mp_obj_t mp_time_time_get(void) {
snvs_lp_srtc_datetime_t t;
SNVS_LP_SRTC_GetDatetime(SNVS, &t);
- // EPOCH is 1970 for this port, which leads to the following trouble:
- // timeutils_seconds_since_epoch() calls timeutils_seconds_since_2000(), and
- // timeutils_seconds_since_2000() subtracts 2000 from year, but uses
- // an unsigned number for seconds, That causes an underrun, which is not
- // fixed by adding the TIMEUTILS_SECONDS_1970_TO_2000.
- // Masking it to 32 bit for year < 2000 fixes it.
- return mp_obj_new_int_from_ull(
+ return timeutils_obj_from_timestamp(
timeutils_seconds_since_epoch(t.year, t.month, t.day, t.hour, t.minute, t.second)
- & (t.year < 2000 ? 0xffffffff : 0xffffffffffff)
);
}
diff --git a/ports/renesas-ra/modtime.c b/ports/renesas-ra/modtime.c
index cbd639721f..e1358f82bc 100644
--- a/ports/renesas-ra/modtime.c
+++ b/ports/renesas-ra/modtime.c
@@ -53,5 +53,5 @@ static mp_obj_t mp_time_time_get(void) {
rtc_init_finalise();
ra_rtc_t time;
ra_rtc_get_time(&time);
- return mp_obj_new_int(timeutils_seconds_since_epoch(time.year, time.month, time.date, time.hour, time.minute, time.second));
+ return timeutils_obj_from_timestamp(timeutils_seconds_since_epoch(time.year, time.month, time.date, time.hour, time.minute, time.second));
}
diff --git a/ports/stm32/modtime.c b/ports/stm32/modtime.c
index ff1495a5d9..87a4536b04 100644
--- a/ports/stm32/modtime.c
+++ b/ports/stm32/modtime.c
@@ -59,5 +59,5 @@ static mp_obj_t mp_time_time_get(void) {
RTC_TimeTypeDef time;
HAL_RTC_GetTime(&RTCHandle, &time, RTC_FORMAT_BIN);
HAL_RTC_GetDate(&RTCHandle, &date, RTC_FORMAT_BIN);
- return mp_obj_new_int(timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds));
+ return timeutils_obj_from_timestamp(timeutils_seconds_since_epoch(2000 + date.Year, date.Month, date.Date, time.Hours, time.Minutes, time.Seconds));
}
diff --git a/ports/stm32/mpconfigport.h b/ports/stm32/mpconfigport.h
index 41ed3d08b7..b910188c5a 100644
--- a/ports/stm32/mpconfigport.h
+++ b/ports/stm32/mpconfigport.h
@@ -76,6 +76,7 @@
#ifndef MICROPY_FLOAT_IMPL // can be configured by each board via mpconfigboard.mk
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
#endif
+#define MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE (1)
#define MICROPY_USE_INTERNAL_ERRNO (1)
#define MICROPY_SCHEDULER_STATIC_NODES (1)
#define MICROPY_SCHEDULER_DEPTH (8)
diff --git a/ports/unix/Makefile b/ports/unix/Makefile
index 3c54d156c3..8bd58a2542 100644
--- a/ports/unix/Makefile
+++ b/ports/unix/Makefile
@@ -52,6 +52,10 @@ CFLAGS += $(INC) $(CWARN) -std=gnu99 -DUNIX $(COPT) -I$(VARIANT_DIR) $(CFLAGS_EX
# This option has no effect on 64-bit builds.
CFLAGS += -D_FILE_OFFSET_BITS=64
+# Force the use of 64-bits for time_t in C library functions on 32-bit platforms.
+# This option has no effect on 64-bit builds.
+CFLAGS += -D_TIME_BITS=64
+
# Debugging/Optimization
ifdef DEBUG
COPT ?= -Og
diff --git a/ports/unix/modtime.c b/ports/unix/modtime.c
index fbd94b5ecd..41b7c89df4 100644
--- a/ports/unix/modtime.c
+++ b/ports/unix/modtime.c
@@ -34,6 +34,7 @@
#include "py/mphal.h"
#include "py/runtime.h"
+#include "shared/timeutils/timeutils.h"
#ifdef _WIN32
static inline int msec_sleep_tv(struct timeval *tv) {
@@ -130,12 +131,7 @@ static mp_obj_t mod_time_gm_local_time(size_t n_args, const mp_obj_t *args, stru
if (n_args == 0) {
t = time(NULL);
} else {
- #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
- mp_float_t val = mp_obj_get_float(args[0]);
- t = (time_t)MICROPY_FLOAT_C_FUN(trunc)(val);
- #else
- t = mp_obj_get_int(args[0]);
- #endif
+ t = (time_t)timeutils_obj_get_timestamp(args[0]);
}
struct tm *tm = time_func(&t);
@@ -196,7 +192,7 @@ static mp_obj_t mod_time_mktime(mp_obj_t tuple) {
if (ret == -1) {
mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("invalid mktime usage"));
}
- return mp_obj_new_int(ret);
+ return timeutils_obj_from_timestamp(ret);
}
MP_DEFINE_CONST_FUN_OBJ_1(mod_time_mktime_obj, mod_time_mktime);
diff --git a/ports/unix/mpconfigport.h b/ports/unix/mpconfigport.h
index 21ce75a351..973b5e74ce 100644
--- a/ports/unix/mpconfigport.h
+++ b/ports/unix/mpconfigport.h
@@ -124,6 +124,9 @@ typedef long mp_off_t;
// VFS stat functions should return time values relative to 1970/1/1
#define MICROPY_EPOCH_IS_1970 (1)
+// port modtime functions use time_t
+#define MICROPY_TIMESTAMP_IMPL (MICROPY_TIMESTAMP_IMPL_TIME_T)
+
// Assume that select() call, interrupted with a signal, and erroring
// with EINTR, updates remaining timeout value.
#define MICROPY_SELECT_REMAINING_TIME (1)
diff --git a/ports/windows/Makefile b/ports/windows/Makefile
index 115d1a61ef..9eee98cdd4 100644
--- a/ports/windows/Makefile
+++ b/ports/windows/Makefile
@@ -46,6 +46,10 @@ LDFLAGS += -lm -lbcrypt $(LDFLAGS_EXTRA)
# This option has no effect on 64-bit builds.
CFLAGS += -D_FILE_OFFSET_BITS=64
+# Force the use of 64-bits for time_t in C library functions on 32-bit platforms.
+# This option has no effect on 64-bit builds.
+CFLAGS += -D_TIME_BITS=64
+
# Debugging/Optimization
ifdef DEBUG
CFLAGS += -g
diff --git a/py/mpconfig.h b/py/mpconfig.h
index cf0538cae4..4c12762759 100644
--- a/py/mpconfig.h
+++ b/py/mpconfig.h
@@ -893,6 +893,64 @@ typedef double mp_float_t;
#define MICROPY_FULL_CHECKS (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_CORE_FEATURES)
#endif
+// Ports can choose to use timestamps based on 2000-01-01 or 1970-01-01
+// Default is timestamps based on 2000-01-01
+#if !defined(MICROPY_EPOCH_IS_2000) && !defined(MICROPY_EPOCH_IS_1970)
+#define MICROPY_EPOCH_IS_2000 (1)
+#define MICROPY_EPOCH_IS_1970 (0)
+#elif !defined(MICROPY_EPOCH_IS_1970)
+#define MICROPY_EPOCH_IS_1970 (1 - (MICROPY_EPOCH_IS_2000))
+#elif !defined(MICROPY_EPOCH_IS_2000)
+#define MICROPY_EPOCH_IS_2000 (1 - (MICROPY_EPOCH_IS_1970))
+#endif
+
+// To maintain reasonable compatibility with CPython on embedded systems,
+// and avoid breaking anytime soon, time functions are defined to work
+// at least between 1970 and 2099 (included) on any machine.
+//
+// Specific ports can enable extended date support
+// - after 2099 using MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND
+// - before 1970 using MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+// The largest possible range is year 1600 to year 3000
+//
+// By default, extended date support is only enabled for machines using 64 bit pointers,
+// but it can be enabled by specific ports
+#ifndef MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+#if MP_SSIZE_MAX > 2147483647
+#define MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE (1)
+#else
+#define MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE (0)
+#endif
+#endif
+
+// When support for dates <1970 is enabled, supporting >=2100 does not cost anything
+#ifndef MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND
+#define MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND (MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE)
+#endif
+
+// The type to be used to represent platform-specific timestamps depends on the choices above
+#define MICROPY_TIMESTAMP_IMPL_LONG_LONG (0)
+#define MICROPY_TIMESTAMP_IMPL_UINT (1)
+#define MICROPY_TIMESTAMP_IMPL_TIME_T (2)
+
+#ifndef MICROPY_TIMESTAMP_IMPL
+#if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE || MICROPY_EPOCH_IS_2000
+#define MICROPY_TIMESTAMP_IMPL (MICROPY_TIMESTAMP_IMPL_LONG_LONG)
+#else
+#define MICROPY_TIMESTAMP_IMPL (MICROPY_TIMESTAMP_IMPL_UINT)
+#endif
+#endif
+
+// `mp_timestamp_t` is the type that should be used by the port
+// to represent timestamps, and is referenced to the platform epoch
+#if MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_LONG_LONG
+typedef long long mp_timestamp_t;
+#elif MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_UINT
+typedef mp_uint_t mp_timestamp_t;
+#elif MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_TIME_T
+typedef time_t mp_timestamp_t;
+#endif
+
// Whether POSIX-semantics non-blocking streams are supported
#ifndef MICROPY_STREAMS_NON_BLOCK
#define MICROPY_STREAMS_NON_BLOCK (MICROPY_CONFIG_ROM_LEVEL_AT_LEAST_EXTRA_FEATURES)
diff --git a/shared/timeutils/timeutils.c b/shared/timeutils/timeutils.c
index 4282a0178d..0c6916e06d 100644
--- a/shared/timeutils/timeutils.c
+++ b/shared/timeutils/timeutils.c
@@ -29,12 +29,27 @@
#include "shared/timeutils/timeutils.h"
-// LEAPOCH corresponds to 2000-03-01, which is a mod-400 year, immediately
-// after Feb 29. We calculate seconds as a signed integer relative to that.
+// To maintain reasonable compatibility with CPython on embedded systems,
+// and avoid breaking anytime soon, timeutils functions are required to
+// work properly between 1970 and 2099 on all ports.
//
-// Our timebase is relative to 2000-01-01.
-
-#define LEAPOCH ((31 + 29) * 86400)
+// During that period of time, leap years occur every 4 years without
+// exception, so we can keep the code short for 32 bit machines.
+
+// The last leap day before the required period is Feb 29, 1968.
+// This is the number of days to add to get to that date.
+#define PREV_LEAP_DAY ((mp_uint_t)(365 + 366 - (31 + 29)))
+#define PREV_LEAP_YEAR 1968
+
+// On ports where either MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND or
+// MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE is enabled, we include extra
+// code to support leap years outside of the 'easy' period.
+// Computation is then made based on 1600 (a mod-400 year).
+// This is the number of days between 1600 and 1968.
+#define QC_BASE_DAY 134409
+#define QC_LEAP_YEAR 1600
+// This is the number of leap days between 1600 and 1970
+#define QC_LEAP_DAYS 89
#define DAYS_PER_400Y (365 * 400 + 97)
#define DAYS_PER_100Y (365 * 100 + 24)
@@ -42,8 +57,20 @@
static const uint16_t days_since_jan1[] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 };
+// type used internally to count small integers relative to epoch
+// (using uint when possible produces smaller code on some platforms)
+#if MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+typedef mp_int_t relint_t;
+#else
+typedef mp_uint_t relint_t;
+#endif
+
bool timeutils_is_leap_year(mp_uint_t year) {
+ #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
+ #else
+ return year % 4 == 0;
+ #endif
}
// month is one based
@@ -65,67 +92,67 @@ mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date) {
return yday;
}
-void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t, timeutils_struct_time_t *tm) {
- // The following algorithm was adapted from musl's __secs_to_tm and adapted
- // for differences in MicroPython's timebase.
-
- mp_int_t seconds = t - LEAPOCH;
+void timeutils_seconds_since_1970_to_struct_time(timeutils_timestamp_t seconds, timeutils_struct_time_t *tm) {
+ // The following algorithm was inspired from musl's __secs_to_tm
+ // and simplified to reduce code footprint in the simple case
- mp_int_t days = seconds / 86400;
+ relint_t days = seconds / 86400;
seconds %= 86400;
+ #if MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
if (seconds < 0) {
seconds += 86400;
days -= 1;
}
+ #endif
tm->tm_hour = seconds / 3600;
tm->tm_min = seconds / 60 % 60;
tm->tm_sec = seconds % 60;
- mp_int_t wday = (days + 2) % 7; // Mar 1, 2000 was a Wednesday (2)
+ relint_t wday = (days + 3) % 7; // Jan 1, 1970 was a Thursday (3)
+ #if MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
if (wday < 0) {
wday += 7;
}
+ #endif
tm->tm_wday = wday;
- mp_int_t qc_cycles = days / DAYS_PER_400Y;
+ days += PREV_LEAP_DAY;
+
+ #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+ // rebase day to the oldest supported date (=> always positive)
+ mp_uint_t base_year = QC_LEAP_YEAR;
+ days += QC_BASE_DAY;
+ mp_uint_t qc_cycles = days / DAYS_PER_400Y;
days %= DAYS_PER_400Y;
- if (days < 0) {
- days += DAYS_PER_400Y;
- qc_cycles--;
- }
- mp_int_t c_cycles = days / DAYS_PER_100Y;
+ mp_uint_t c_cycles = days / DAYS_PER_100Y;
if (c_cycles == 4) {
c_cycles--;
}
days -= (c_cycles * DAYS_PER_100Y);
-
- mp_int_t q_cycles = days / DAYS_PER_4Y;
+ #else
+ mp_uint_t base_year = PREV_LEAP_YEAR;
+ mp_uint_t qc_cycles = 0;
+ mp_uint_t c_cycles = 0;
+ #endif
+
+ mp_uint_t q_cycles = days / DAYS_PER_4Y;
+ #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
if (q_cycles == 25) {
q_cycles--;
}
+ #endif
days -= q_cycles * DAYS_PER_4Y;
- mp_int_t years = days / 365;
+ relint_t years = days / 365;
if (years == 4) {
years--;
}
days -= (years * 365);
- /* We will compute tm_yday at the very end
- mp_int_t leap = !years && (q_cycles || !c_cycles);
-
- tm->tm_yday = days + 31 + 28 + leap;
- if (tm->tm_yday >= 365 + leap) {
- tm->tm_yday -= 365 + leap;
- }
-
- tm->tm_yday++; // Make one based
- */
-
- tm->tm_year = 2000 + years + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
+ tm->tm_year = base_year + years + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles;
// Note: days_in_month[0] corresponds to March
- static const int8_t days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29};
+ static const uint8_t days_in_month[] = {31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29};
mp_int_t month;
for (month = 0; days_in_month[month] <= days; month++) {
@@ -144,21 +171,28 @@ void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t, timeutils_struct_t
}
// returns the number of seconds, as an integer, since 2000-01-01
-mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month,
+timeutils_timestamp_t timeutils_seconds_since_1970(mp_uint_t year, mp_uint_t month,
mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
- return
- second
- + minute * 60
- + hour * 3600
- + (timeutils_year_day(year, month, date) - 1
- + ((year - 2000 + 3) / 4) // add a day each 4 years starting with 2001
- - ((year - 2000 + 99) / 100) // subtract a day each 100 years starting with 2001
- + ((year - 2000 + 399) / 400) // add a day each 400 years starting with 2001
- ) * 86400
- + (year - 2000) * 31536000;
+ #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+ mp_uint_t ref_year = QC_LEAP_YEAR;
+ #else
+ mp_uint_t ref_year = PREV_LEAP_YEAR;
+ #endif
+ timeutils_timestamp_t res;
+ res = ((relint_t)year - 1970) * 365;
+ res += (year - (ref_year + 1)) / 4; // add a day each 4 years
+ #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+ res -= (year - (ref_year + 1)) / 100; // subtract a day each 100 years
+ res += (year - (ref_year + 1)) / 400; // add a day each 400 years
+ res -= QC_LEAP_DAYS;
+ #endif
+ res += timeutils_year_day(year, month, date) - 1;
+ res *= 86400;
+ res += hour * 3600 + minute * 60 + second;
+ return res;
}
-mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday,
+timeutils_timestamp_t timeutils_mktime_1970(mp_uint_t year, mp_int_t month, mp_int_t mday,
mp_int_t hours, mp_int_t minutes, mp_int_t seconds) {
// Normalize the tuple. This allows things like:
@@ -211,12 +245,16 @@ mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday,
year++;
}
}
- return timeutils_seconds_since_2000(year, month, mday, hours, minutes, seconds);
+ return timeutils_seconds_since_1970(year, month, mday, hours, minutes, seconds);
}
// Calculate the weekday from the date.
// The result is zero based with 0 = Monday.
// by Michael Keith and Tom Craver, 1990.
int timeutils_calc_weekday(int y, int m, int d) {
- return ((d += m < 3 ? y-- : y - 2, 23 * m / 9 + d + 4 + y / 4 - y / 100 + y / 400) + 6) % 7;
+ return ((d += m < 3 ? y-- : y - 2, 23 * m / 9 + d + 4 + y / 4
+ #if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+ - y / 100 + y / 400
+ #endif
+ ) + 6) % 7;
}
diff --git a/shared/timeutils/timeutils.h b/shared/timeutils/timeutils.h
index 874d16e976..35356b462a 100644
--- a/shared/timeutils/timeutils.h
+++ b/shared/timeutils/timeutils.h
@@ -27,9 +27,23 @@
#ifndef MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H
#define MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H
+#include "py/obj.h"
+#if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
+#include <math.h> // required for trunc()
+#endif
+
+// `timeutils_timestamp_t` is the type used internally by timeutils to
+// represent timestamps, and is always referenced to 1970.
+// It may not match the platform-specific `mp_timestamp_t`.
+#if MICROPY_TIME_SUPPORT_Y2100_AND_BEYOND || MICROPY_TIME_SUPPORT_Y1969_AND_BEFORE
+typedef long long timeutils_timestamp_t;
+#else
+typedef mp_uint_t timeutils_timestamp_t;
+#endif
+
// The number of seconds between 1970/1/1 and 2000/1/1 is calculated using:
// time.mktime((2000,1,1,0,0,0,0,0,0)) - time.mktime((1970,1,1,0,0,0,0,0,0))
-#define TIMEUTILS_SECONDS_1970_TO_2000 (946684800ULL)
+#define TIMEUTILS_SECONDS_1970_TO_2000 (946684800LL)
typedef struct _timeutils_struct_time_t {
uint16_t tm_year; // i.e. 2014
@@ -45,66 +59,116 @@ typedef struct _timeutils_struct_time_t {
bool timeutils_is_leap_year(mp_uint_t year);
mp_uint_t timeutils_days_in_month(mp_uint_t year, mp_uint_t month);
mp_uint_t timeutils_year_day(mp_uint_t year, mp_uint_t month, mp_uint_t date);
+int timeutils_calc_weekday(int y, int m, int d);
-void timeutils_seconds_since_2000_to_struct_time(mp_uint_t t,
+void timeutils_seconds_since_1970_to_struct_time(timeutils_timestamp_t t,
timeutils_struct_time_t *tm);
// Year is absolute, month/date are 1-based, hour/minute/second are 0-based.
-mp_uint_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month,
+timeutils_timestamp_t timeutils_seconds_since_1970(mp_uint_t year, mp_uint_t month,
mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second);
// Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based.
-mp_uint_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday,
+timeutils_timestamp_t timeutils_mktime_1970(mp_uint_t year, mp_int_t month, mp_int_t mday,
mp_int_t hours, mp_int_t minutes, mp_int_t seconds);
+static inline mp_timestamp_t timeutils_obj_get_timestamp(mp_obj_t o_in) {
+ #if MICROPY_PY_BUILTINS_FLOAT && MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_DOUBLE
+ mp_float_t val = mp_obj_get_float(o_in);
+ return (mp_timestamp_t)MICROPY_FLOAT_C_FUN(trunc)(val);
+ #elif MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_UINT
+ return mp_obj_get_uint(o_in);
+ #else
+ return mp_obj_get_ll(o_in);
+ #endif
+}
+
+static inline mp_obj_t timeutils_obj_from_timestamp(mp_timestamp_t t) {
+ #if MICROPY_TIMESTAMP_IMPL == MICROPY_TIMESTAMP_IMPL_UINT
+ return mp_obj_new_int_from_uint(t);
+ #else
+ return mp_obj_new_int_from_ll(t);
+ #endif
+}
+
+static inline void timeutils_seconds_since_2000_to_struct_time(mp_timestamp_t t, timeutils_struct_time_t *tm) {
+ timeutils_seconds_since_1970_to_struct_time((timeutils_timestamp_t)(t + TIMEUTILS_SECONDS_1970_TO_2000), tm);
+}
+
+// Year is absolute, month/date are 1-based, hour/minute/second are 0-based.
+static inline mp_timestamp_t timeutils_seconds_since_2000(mp_uint_t year, mp_uint_t month, mp_uint_t date,
+ mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
+ return (mp_timestamp_t)timeutils_seconds_since_1970(year, month, date, hour, minute, second) - TIMEUTILS_SECONDS_1970_TO_2000;
+}
+
+// Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based.
+static inline mp_timestamp_t timeutils_mktime_2000(mp_uint_t year, mp_int_t month, mp_int_t mday,
+ mp_int_t hours, mp_int_t minutes, mp_int_t seconds) {
+ return (mp_timestamp_t)timeutils_mktime_1970(year, month, mday, hours, minutes, seconds) - TIMEUTILS_SECONDS_1970_TO_2000;
+}
+
+
// Select the Epoch used by the port.
#if MICROPY_EPOCH_IS_1970
-static inline void timeutils_seconds_since_epoch_to_struct_time(uint64_t t, timeutils_struct_time_t *tm) {
- // TODO this will give incorrect results for dates before 2000/1/1
- timeutils_seconds_since_2000_to_struct_time((mp_uint_t)(t - TIMEUTILS_SECONDS_1970_TO_2000), tm);
+static inline void timeutils_seconds_since_epoch_to_struct_time(mp_timestamp_t t, timeutils_struct_time_t *tm) {
+ timeutils_seconds_since_1970_to_struct_time(t, tm);
+}
+
+// Year is absolute, month/date are 1-based, hour/minute/second are 0-based.
+static inline mp_timestamp_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint_t date,
+ mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
+ return timeutils_seconds_since_1970(year, month, date, hour, minute, second);
}
// Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based.
-static inline uint64_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday, mp_int_t hours, mp_int_t minutes, mp_int_t seconds) {
- return timeutils_mktime_2000(year, month, mday, hours, minutes, seconds) + TIMEUTILS_SECONDS_1970_TO_2000;
+static inline mp_timestamp_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday,
+ mp_int_t hours, mp_int_t minutes, mp_int_t seconds) {
+ return timeutils_mktime_1970(year, month, mday, hours, minutes, seconds);
}
-// Year is absolute, month/date are 1-based, hour/minute/second are 0-based.
-static inline uint64_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month,
- mp_uint_t date, mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
- // TODO this will give incorrect results for dates before 2000/1/1
- return timeutils_seconds_since_2000(year, month, date, hour, minute, second) + TIMEUTILS_SECONDS_1970_TO_2000;
+static inline mp_timestamp_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(int64_t ns) {
+ return (mp_timestamp_t)(ns / 1000000000LL);
}
-static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) {
- return (mp_uint_t)(ns / 1000000000ULL);
+static inline int64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_timestamp_t s) {
+ return (int64_t)s * 1000000000LL;
}
-static inline uint64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(uint64_t ns) {
+static inline int64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(int64_t ns) {
return ns;
}
#else // Epoch is 2000
-#define timeutils_seconds_since_epoch_to_struct_time timeutils_seconds_since_2000_to_struct_time
-#define timeutils_seconds_since_epoch timeutils_seconds_since_2000
-#define timeutils_mktime timeutils_mktime_2000
+static inline void timeutils_seconds_since_epoch_to_struct_time(mp_timestamp_t t, timeutils_struct_time_t *tm) {
+ timeutils_seconds_since_2000_to_struct_time(t, tm);
+}
+
+// Year is absolute, month/date are 1-based, hour/minute/second are 0-based.
+static inline mp_timestamp_t timeutils_seconds_since_epoch(mp_uint_t year, mp_uint_t month, mp_uint_t date,
+ mp_uint_t hour, mp_uint_t minute, mp_uint_t second) {
+ return timeutils_seconds_since_2000(year, month, date, hour, minute, second);
+}
+
+// Year is absolute, month/mday are 1-based, hours/minutes/seconds are 0-based.
+static inline mp_timestamp_t timeutils_mktime(mp_uint_t year, mp_int_t month, mp_int_t mday,
+ mp_int_t hours, mp_int_t minutes, mp_int_t seconds) {
+ return timeutils_mktime_2000(year, month, mday, hours, minutes, seconds);
+}
-static inline uint64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_uint_t s) {
- return ((uint64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000ULL;
+static inline mp_timestamp_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(int64_t ns) {
+ return (mp_timestamp_t)(ns / 1000000000LL - TIMEUTILS_SECONDS_1970_TO_2000);
}
-static inline mp_uint_t timeutils_seconds_since_epoch_from_nanoseconds_since_1970(uint64_t ns) {
- return ns / 1000000000ULL - TIMEUTILS_SECONDS_1970_TO_2000;
+static inline int64_t timeutils_seconds_since_epoch_to_nanoseconds_since_1970(mp_timestamp_t s) {
+ return ((int64_t)s + TIMEUTILS_SECONDS_1970_TO_2000) * 1000000000LL;
}
static inline int64_t timeutils_nanoseconds_since_epoch_to_nanoseconds_since_1970(int64_t ns) {
- return ns + TIMEUTILS_SECONDS_1970_TO_2000 * 1000000000ULL;
+ return ns + TIMEUTILS_SECONDS_1970_TO_2000 * 1000000000LL;
}
#endif
-int timeutils_calc_weekday(int y, int m, int d);
-
#endif // MICROPY_INCLUDED_LIB_TIMEUTILS_TIMEUTILS_H
diff --git a/tests/extmod/time_mktime.py b/tests/extmod/time_mktime.py
new file mode 100644
index 0000000000..7fc643dc3c
--- /dev/null
+++ b/tests/extmod/time_mktime.py
@@ -0,0 +1,120 @@
+# test conversion from date tuple to timestamp and back
+
+try:
+ import time
+
+ time.localtime
+except (ImportError, AttributeError):
+ print("SKIP")
+ raise SystemExit
+
+# Range of date expected to work on all MicroPython platforms
+MIN_YEAR = 1970
+MAX_YEAR = 2099
+# CPython properly supported date range:
+# - on Windows: year 1970 to 3000+
+# - on Unix: year 1583 to 3000+
+
+# Start test from Jan 1, 2001 13:00 (Feb 2000 might already be broken)
+SAFE_DATE = (2001, 1, 1, 13, 0, 0, 0, 0, -1)
+
+
+# mktime function that checks that the result is reversible
+def safe_mktime(date_tuple):
+ try:
+ res = time.mktime(date_tuple)
+ chk = time.localtime(res)
+ except OverflowError:
+ print("safe_mktime:", date_tuple, "overflow error")
+ return None
+ if chk[0:5] != date_tuple[0:5]:
+ print("safe_mktime:", date_tuple[0:5], " -> ", res, " -> ", chk[0:5])
+ return None
+ return res
+
+
+# localtime function that checks that the result is reversible
+def safe_localtime(timestamp):
+ try:
+ res = time.localtime(timestamp)
+ chk = time.mktime(res)
+ except OverflowError:
+ print("safe_localtime:", timestamp, "overflow error")
+ return None
+ if chk != timestamp:
+ print("safe_localtime:", timestamp, " -> ", res, " -> ", chk)
+ return None
+ return res
+
+
+# look for smallest valid timestamps by iterating backwards on tuple
+def test_bwd(date_tuple):
+ curr_stamp = safe_mktime(date_tuple)
+ year = date_tuple[0]
+ month = date_tuple[1] - 1
+ if month < 1:
+ year -= 1
+ month = 12
+ while year >= MIN_YEAR:
+ while month >= 1:
+ next_tuple = (year, month) + date_tuple[2:]
+ next_stamp = safe_mktime(next_tuple)
+ # at this stage, only test consistency and monotonicity
+ if next_stamp is None or next_stamp >= curr_stamp:
+ return date_tuple
+ date_tuple = next_tuple
+ curr_stamp = next_stamp
+ month -= 1
+ year -= 1
+ month = 12
+ return date_tuple
+
+
+# test day-by-day to ensure that every date is properly converted
+def test_fwd(start_date):
+ DAYS_PER_MONTH = (0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31)
+ curr_stamp = safe_mktime(start_date)
+ curr_date = safe_localtime(curr_stamp)
+ while curr_date[0] <= MAX_YEAR:
+ if curr_date[2] < 15:
+ skip_days = 13
+ else:
+ skip_days = 1
+ next_stamp = curr_stamp + skip_days * 86400
+ next_date = safe_localtime(next_stamp)
+ if next_date is None:
+ return curr_date
+ if next_date[2] != curr_date[2] + skip_days:
+ # next month
+ if next_date[2] != 1:
+ print("wrong day of month:", next_date)
+ return curr_date
+ # check the number of days in previous month
+ month_days = DAYS_PER_MONTH[curr_date[1]]
+ if month_days == 28 and curr_date[0] % 4 == 0:
+ if curr_date[0] % 100 != 0 or curr_date[0] % 400 == 0:
+ month_days += 1
+ if curr_date[2] != month_days:
+ print("wrong day count in prev month:", curr_date[2], "vs", month_days)
+ return curr_date
+ if next_date[1] != curr_date[1] + 1:
+ # next year
+ if curr_date[1] != 12:
+ print("wrong month count in prev year:", curr_date[1])
+ return curr_date
+ if next_date[1] != 1:
+ print("wrong month:", next_date)
+ return curr_date
+ if next_date[0] != curr_date[0] + 1:
+ print("wrong year:", next_date)
+ return curr_date
+ curr_stamp = next_stamp
+ curr_date = next_date
+ return curr_date
+
+
+small_date = test_bwd(SAFE_DATE)
+large_date = test_fwd(small_date)
+print("tested from", small_date[0:3], "to", large_date[0:3])
+print(small_date[0:3], "wday is", small_date[6])
+print(large_date[0:3], "wday is", large_date[6])