timezone support in MicroPython ?

All ESP8266 boards running MicroPython.
Official boards are the Adafruit Huzzah and Feather boards.
Target audience: MicroPython users with an ESP8266 board.
anyone14959
Posts: 1
Joined: Sun Feb 06, 2022 6:31 pm

Re: timezone support in MicroPython ?

Post by anyone14959 » Sun Feb 06, 2022 6:41 pm

For me this works perfect and returns a tuple with correct localtime with tz and dst

Code: Select all

def truetime_calc(tz = 1): # timezone    
    dst = 0
    cur_year = utime.localtime()[0]
    start_dst = [cur_year,3,24,2,0,0,0,0]
    end_dst = [cur_year,10,24,3,0,0,0,0]
    start_dst[2] += 6 - utime.localtime(utime.mktime(start_dst))[6]
    end_dst[2] += 6 - utime.localtime(utime.mktime(end_dst))[6]
    start_dst = utime.mktime(start_dst)
    end_dst = utime.mktime(end_dst)
    if start_dst < (utime.time() + tz * 3600) < end_dst:
        dst = 1
    else:
        dst = 0
    return utime.localtime(utime.time() + tz * 3600 + dst * 3600)

davef
Posts: 811
Joined: Thu Apr 30, 2020 1:03 am
Location: Christchurch, NZ

Re: timezone support in MicroPython ?

Post by davef » Sun Feb 06, 2022 8:08 pm

Thanks for another example. It reminded me that I need to change the equivalent of start_dst and end_dst in my system as it is a new year. Fortunately we don't change back NZST until March :)

paulsk
Posts: 11
Joined: Tue Jun 01, 2021 9:54 am
Location: Lisbon
Contact:

Re: How about this appoach?

Post by paulsk » Tue Jul 19, 2022 8:12 pm

pythoncoder wrote:
Mon Jul 30, 2018 4:31 pm
This doesn't take account of the time of day at which the change occurs.

The approach I'd use is to write a program on a PC which (for my own locale) calculated the time of the start and end of DST for each year of interest. The times would be in seconds since the MicroPython epoch. The output of the script would be a Python dict indexed by year and containing tuples of form (dst_start, dst_end) - (numbers below are arbitrary).

Code: Select all

dst = {2018:(12345, 67890), 2019:(88888, 99999),}
def ltime():
    t = utime.time()
    start, end = dst[utime.localtime(t)[0]
    return t if t < start or t > end else t + 3600
The same thing could be done with a file if the dict was regarded as being excessively big.

In either case the calculation is done once, offline on a PC, minimising the work to be done at runtime.
Very nice solution Peter! Thank you!
I immediately used it in my NTP-client script for local timezone (Europe/Portugal). Created a dst dictionary that spans ten years:

Code: Select all

# See: https://www.epochconverter.com/
# Values are for timezone Europe/Portugal
dst = {
        2022:(1648342800, 1667095200),  # 2022-03-29 01:00:00 / 2022-10-30 02:00:00
        2023:(1679792400, 1698544800),  # 2023-03-26 01:00:00 / 2023-10-29 02:00:00
        2024:(1711846800, 1729994400),  # 2024-03-31 01:00:00 / 2024-10-27 02:00:00
        2025:(1743296400, 1761444000),  # 2025 03-30 01:00:00 / 2025-10-28 02:00:00
        2026:(1774746000, 1792893600),  # 2026-03-29 01:00:00 / 2026-10-25 02:00:00
        2027:(1806195600, 1824948000),  # 2027-03-28 01:00:00 / 2027-10-31 02:00:00
        2028:(1837645200, 1856397600),  # 2028-03-26 01:00:00 / 2028-10-29 02:00:00
        2029:(1869094800, 1887847200),  # 2029-03-25 01:00:00 / 2029-10-28 02:00:00
        2030:(1901149200, 1919296800),  # 2030-03-31 01:00:00 / 2030-10-27 02:00:00
        2031:(1932598800, 1950746400),  # 2031-03-30 01:00:00 / 2031-10-26 02:00:00
} 
My version of an is_dst() function:

Code: Select all

def is_dst():
    t = utime.time()
    yr = utime.localtime(t)[0]

    if yr in dst.keys():
        start, end = dst[yr]
        return False if t < start or t > end else True
    else:
        print("year: {} not in dst dictionary ({}).\nUpdate the dictionary! Exiting...".format(yr, dst.keys()))
        raise SystemExit

Beormund
Posts: 7
Joined: Wed Jul 27, 2022 2:31 pm

Re: timezone support in MicroPython ?

Post by Beormund » Wed Jul 27, 2022 2:44 pm

I wrote a small module to convert UTC to Local Time using daylight saving policies. It might help.

See my gist for latest version and bug fixes:
https://gist.github.com/Beormund/f0f39c ... d1964c9627

Code: Select all

import utime

# hemisphere [0 = Northern, 1 = Southern]
# week [0 = last week of month, 1..4 = first..fourth]
# month [1 = January; 12 = December]
# weekday [0 = Monday; 6 = Sunday] (day of week) 
# hour (hour at which dst/std changes)
# timezone [-780..780] (offset from UTC in MINUTES - 780min / 60min=13hrs)
class Policy:
    def __init__(self, hemisphere, week, month, weekday, hour, timezone):
        if hemisphere not in [0,1]: raise ValueError('hemisphere must be 0..1')
        if week not in range (0,5): raise ValueError('week must be 0 or 1..4')
        if month not in range (1,13): raise ValueError('month must be 1..12')
        if weekday not in range(0,7): raise ValueError('weekday must be 0..6')
        if hour not in range(0,24): raise ValueError('hour must be 0..23')
        if timezone not in range(-780,781): raise ValueError('weekday must be -780..780')
        self.hemisphere = hemisphere
        self.week = week
        self.month = month
        self.weekday = weekday
        self.hour = hour
        self.timezone = timezone
    def __str__(self):
        self.days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sum']
        self.abbr = ['last', 'first', 'second', 'third', 'fourth']
        self.months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
        return ('Daylight Saving Policy: daylight saving {} on the {} {} of {} at {:02}:00 hrs (UTC{})').format(
            self.prefix,
            self.abbr[self.week],
            self.days[self.weekday],
            self.months[self.month-1],
            self.hour,
            '' if not self.timezone else '{:+1}'.format(self.timezone/60)
            )
        
class StandardTimePolicy(Policy):
    def __init__(self, hemisphere, week, month, weekday, hour, timezone):
        self.prefix = 'ends'
        super(StandardTimePolicy, self).__init__(hemisphere, week, month, weekday, hour, timezone)

class DaylightSavingPolicy(Policy):
    def __init__(self, hemisphere, week, month, weekday, hour, timezone):
        self.prefix = 'starts'
        super(DaylightSavingPolicy, self).__init__(hemisphere, week, month, weekday, hour, timezone)

class DaylightSaving:
    def __init__(self, dstp, stdp):
        self.dstp = dstp
        self.stdp = stdp
        print(self.dstp)
        print(self.stdp)

    def isleapyear(self, year):
        return ( year % 4 == 0 and year % 100 != 0) or year % 400 ==  0
    
    def dayofmonth(self, week, month, weekday, day, year):
        # Get the first or last day of the month
        t = utime.mktime((year, month, day, 0, 0, 0, 0, 0))
        # Get the weekday of the first or last day of the month
        d = utime.localtime(t)[6]
        increment = lambda d: d + 1 if d < 6 else 0
        decrement = lambda d: d - 1 if d > 0 else 6
        while d != weekday:
            # Increment if start of month else decrement
            day = day + 1 if week else day - 1
            d = increment(d) if week else decrement(d)
        # Increment day of month by number of weeks
        return day + (week - 1) * 7 if week else day
    
    def nthweekday(self, week, month, weekday, hour, year):
        monthlength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
        if self.isleapyear(year):
            monthlength[1] = 29
        day = 1 if week else monthlength[month-1]
        day = self.dayofmonth(week, month, weekday, day, year)
        return utime.mktime((year, month, day, hour, 0, 0, 0, 0))
    
    def gettfromp(self, p, year):
        return self.nthweekday(p.week, p.month, p.weekday, p.hour, year)
    
    def localtime(self, utc):
        print(f'UTC: {self.ftime(utc)}')
        year = utime.gmtime(utc)[0]
        dst = self.gettfromp(self.dstp, year)
        print(f'Daylight Saving Starts: {self.ftime(dst)}')
        std = self.gettfromp(self.stdp, year)
        print(f'Daylight Saving Ends: {self.ftime(std)}')
        saving = False
        if self.stdp.hemisphere:
            # Southern hemisphere
            saving = utc > dst or utc < std
        else:
            # Northern hemisphere
            saving = utc > dst and utc < std
        offset = self.dstp.timezone if saving else self.stdp.timezone
        local = utc + (offset * 60)
        print(f'Local: {self.ftime(local)}')
        return utc + (offset * 60)
    
    def ftime(self, t):
        year, month, day, hour, minute, second, ms, dayinyear = utime.localtime(t)
        return "{:4}-{:02}-{:02}T{:02}:{:02}:{:02}".format(year, month, day, hour, minute, second)
To use:

Code: Select all

import daylightsaving
import utime

# Example for UK Daylight Saving (BST)

# Daylight Saving starts on last Sunday of March at 1AM
# 0 = Northern hemisphere
# 0 = Last week of the month
# 3 = March
# 6 = Sunday
# 1 = 1AM
# Time offset = 60 mins (GMT+1)
dst = DaylightSavingPolicy(0, 0, 3, 6, 1, 60)

# Daylight Saving ends on last Snday of October at 2AM
# 0 = Northern hemisphere
# 0 = Last week of the month
# 10 = October
# 6 = Sunday
# 2 = 2AM
# Time offset = 0 mins (GMT/UTC)
std = StandardTimePolicy(0, 0, 10, 6, 2, 0)

# Create a DaylightSaving object passing in policies
ds = DaylightSaving(dst, std)

# Obtain UTC time in seconds since epoch (from NTP Server or RTC etc)
utc = utime.mktime((2022, 7, 27, 13, 30, 0, 0, 0))

# Calculate local time passing in UTC time
local = ds.localtime(utc)
Please leave comments on the gist if you find any problems or can suggest improvements.

Post Reply