You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
320 lines
8.4 KiB
Python
320 lines
8.4 KiB
Python
#! /usr/bin/env python3
|
|
"""
|
|
Method to convert time - in seconds passed since Unix epoch - from GMT time zone to given time zone expressed in Posix Time Zone format.
|
|
|
|
:author: Roberto Bellingeri
|
|
:copyright: Copyright 2023 - NetGuru
|
|
:license: GPL
|
|
"""
|
|
|
|
"""
|
|
Changelog:
|
|
|
|
0.0.1
|
|
initial release
|
|
0.0.2
|
|
changed returned format
|
|
0.0.3
|
|
fixed bug in leap year calculation
|
|
"""
|
|
|
|
__version__ = "0.0.3"
|
|
|
|
|
|
import time
|
|
import re
|
|
|
|
def checkptz(ptz_string: str):
|
|
"""
|
|
Check if the format of the string complies with what is described here: https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
|
|
Only for testing purposes: on MicroPython always return 'None'.
|
|
|
|
Parameters:
|
|
ptz_string (str): String in Posix Time Zone format
|
|
|
|
Returns:
|
|
bool: Test result
|
|
"""
|
|
ptz_string = _normalize(ptz_string)
|
|
|
|
result = None
|
|
|
|
if hasattr(re, 'fullmatch'): # re.fullmatch() is not defined in MicroPython
|
|
check_re = r"^"
|
|
check_re += r"[^:\d+-]{3,}" # std name
|
|
check_re += r"[+-]?\d{1,2}(?::\d{1,2}){0,2}" # std offset
|
|
|
|
check_re += r"(?:" # dst zone begin
|
|
check_re += r"[^:\d+-,]{3,}" # dst name
|
|
check_re += r"(?:" # dst offset begin
|
|
check_re += r"(?:[+-]?\d{1,2}(?::\d{1,2}){0,2})?" # dst offset time, can be omitted
|
|
check_re += r"(?:,(?:J\d{1,3}|\d{1,3}|M(?:[1-9]|1[0-2])\.[1-5]\.[0-6])(?:\/\d{1,2}(?::\d{1,2}){0,2})?){2}" #dst start/end date - time can be omitted
|
|
check_re += r")" # dst offset end
|
|
check_re += r")?" # dst zone finish, can be omitted
|
|
|
|
check_re += r"$"
|
|
|
|
#print(check_re)
|
|
|
|
if (re.fullmatch(check_re,ptz_string) == None): # type: ignore
|
|
result = False
|
|
else:
|
|
result = True
|
|
|
|
return result
|
|
|
|
|
|
def tztime(timestamp: float, ptz_string: str):
|
|
"""
|
|
Converts a time expressed in seconds in struct_time style 9-tuple according to the time zone provided in Posix Time Zone format
|
|
|
|
Parameters:
|
|
timestamp (float): Time in second
|
|
ptz_string (str): Time zone in Posix format
|
|
|
|
Returns:
|
|
struct_time style tuple:
|
|
* ``year``
|
|
* ``month``
|
|
* ``mday``
|
|
* ``hour``
|
|
* ``minute``
|
|
* ``second``
|
|
* ``weekday``
|
|
* ``yearday``
|
|
* ``isdst``
|
|
"""
|
|
return _timecalc(timestamp, ptz_string)[:9]
|
|
|
|
|
|
def tziso(timestamp: float, ptz_string: str, zone_designator = True):
|
|
"""
|
|
Return an ISO 8601 date and time string according to the time zone provided in Posix Time Zone format
|
|
|
|
Parameters:
|
|
timestamp (float): Time in second
|
|
ptz_string (str): Time Zone in Posix format
|
|
zone_designator (bool): Insert zone designator in returned string - default: True
|
|
|
|
Returns:
|
|
string: ISO 8601 date and time string
|
|
"""
|
|
tx = _timecalc(timestamp, ptz_string)
|
|
|
|
stx = f"{tx[0]}-{tx[1]:02d}-{tx[2]:02d}T{tx[3]:02d}:{tx[4]:02d}:{tx[5]:02d}"
|
|
if (zone_designator == True):
|
|
if (tx[9] != 0):
|
|
stx += f"{(tx[9] // 3600):+03d}"
|
|
|
|
mins = abs(tx[9]) % 3600
|
|
if (mins != 0):
|
|
stx += ":" + f"{(mins // 60):02d}"
|
|
else:
|
|
stx += "Z"
|
|
|
|
return stx
|
|
|
|
|
|
def _timecalc(timestamp: float, ptz_string: str):
|
|
"""
|
|
Converts a time expressed in seconds in 10-tuple according to the time zone provided in Posix Time Zone format
|
|
|
|
Parameters:
|
|
timestamp (float): Time in second
|
|
ptz_string (str): Time zone in Posix format
|
|
zone_designator (bool): Insert zone designator in returned string - default: True
|
|
|
|
Returns:
|
|
tuple:
|
|
* ``year``
|
|
* ``month``
|
|
* ``mday``
|
|
* ``hour``
|
|
* ``minute``
|
|
* ``second``
|
|
* ``weekday``
|
|
* ``yearday``
|
|
* ``isdst``
|
|
* ``utcoffset``
|
|
"""
|
|
ptz_string = _normalize(ptz_string)
|
|
|
|
std_offset_seconds = 0
|
|
dst_offset_seconds = 0
|
|
tot_offset_seconds = 0
|
|
is_dst = False
|
|
|
|
ptz_parts = ptz_string.split(",")
|
|
|
|
#offsetHours = re.split(r"[^\d\+\-\:]+", ptz_parts[0])
|
|
offsetHours = re.compile(r"[^\d\+\-\:]+").split(ptz_parts[0]) # re.compile() is used for MicroPython compatibility
|
|
offsetHours = list(filter(None, offsetHours))
|
|
#print(offsetHours)
|
|
|
|
if (len(offsetHours) > 0):
|
|
|
|
std_offset_seconds = - _hours2secs(offsetHours[0])
|
|
|
|
if (len(offsetHours)>1):
|
|
dst_offset_seconds = _hours2secs(offsetHours[1])
|
|
else:
|
|
dst_offset_seconds = 3600
|
|
|
|
#print("timestamp:\t" + str(int(timestamp)))
|
|
|
|
if (len(ptz_parts)==3):
|
|
year = time.gmtime(int(timestamp))[0]
|
|
dst_start = _parseposixtransition(ptz_parts[1], year)
|
|
dst_end = _parseposixtransition(ptz_parts[2], year)
|
|
|
|
if (dst_start < dst_end): #northern hemisphere
|
|
if ((timestamp + std_offset_seconds) < dst_start):
|
|
is_dst = False
|
|
tot_offset_seconds = std_offset_seconds
|
|
elif ((timestamp + std_offset_seconds + dst_offset_seconds) < dst_end):
|
|
is_dst = True
|
|
tot_offset_seconds = std_offset_seconds + dst_offset_seconds
|
|
else:
|
|
is_dst = False
|
|
tot_offset_seconds = std_offset_seconds
|
|
else: # southern hemisphere
|
|
if ((timestamp + std_offset_seconds + dst_offset_seconds) < dst_end):
|
|
is_dst = True
|
|
tot_offset_seconds = std_offset_seconds + dst_offset_seconds
|
|
elif ((timestamp + std_offset_seconds) < dst_start):
|
|
is_dst = False
|
|
tot_offset_seconds = std_offset_seconds
|
|
else:
|
|
is_dst = True
|
|
tot_offset_seconds = std_offset_seconds + dst_offset_seconds
|
|
|
|
#print("dstOffset:\t" + str(dst_offset_seconds))
|
|
#print("dstStart:\t" + str(dst_start) + "\t" + str(time.gmtime(dst_start)))
|
|
#print("dstEnd: \t" + str(dst_end) + "\t" + str(time.gmtime(dst_end)))
|
|
|
|
else:
|
|
tot_offset_seconds = std_offset_seconds
|
|
|
|
timemod = timestamp + tot_offset_seconds
|
|
|
|
t = time.gmtime(int(timemod))
|
|
|
|
tx = (t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7], int(is_dst), tot_offset_seconds)
|
|
|
|
return tx
|
|
|
|
|
|
def _normalize(ptz_string: str):
|
|
"""
|
|
Return simple normalization of PTZ string
|
|
|
|
Parameters:
|
|
ptz_string (str): PTZ string
|
|
|
|
Returns:
|
|
str: Normalized PTZ string
|
|
"""
|
|
ptz_string = ptz_string.upper()
|
|
ptz_string = re.compile(r"\<[^\>]*\>").sub("DUMMY",ptz_string) # For what appear to be non-standard strings like "<+11>-11<+12>,M10.1.0,M4.1.0/3"
|
|
|
|
return ptz_string
|
|
|
|
|
|
def _parseposixtransition(transition: str, year: int):
|
|
"""
|
|
Returns the moment of the transition from std to dst and vice-versa
|
|
|
|
Parameters:
|
|
transition (str): Part of Posix Time Zone string related to the transition
|
|
year (int): The year
|
|
|
|
Returns:
|
|
float: Time adjusted
|
|
"""
|
|
parts = transition.split('/')
|
|
seconds = 0
|
|
tr = 0
|
|
|
|
if (len(parts) == 2):
|
|
seconds = _hours2secs(parts[1])
|
|
|
|
else:
|
|
seconds = 2 * 3600
|
|
|
|
|
|
if (transition[0] == "M"):
|
|
# 'Mm.n.d' format.
|
|
|
|
date_parts = parts[0][1:].split('.')
|
|
if (len(date_parts)==3):
|
|
month = int(date_parts[0]) # month from '1' to '12'
|
|
week_of_month = int(date_parts[1]) # week number from '1' to '5'. '5' always the last.
|
|
day_of_week = int(date_parts[2]) # day of week - 0:Sunday 1:Monday 2:Tuesday 3:Wednesday 4:Thursday 5:Friday 6:Saturday
|
|
|
|
base_year = 1970
|
|
base_year_1st_day = 4 # the first day of the year 1970 was Thursday
|
|
|
|
month_days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
|
if ((((year % 4) == 0) and ((year % 100) != 0)) or (year % 400) == 0):
|
|
month_days[1] = 29
|
|
|
|
# calculate the number of days since 1/1/base_year
|
|
days_since_base_date = (year - base_year) * 365
|
|
|
|
for y in range(base_year, year):
|
|
if ((((y % 4) == 0) and ((y % 100) != 0)) or (y % 400) == 0):
|
|
days_since_base_date += 1
|
|
|
|
days_since_base_date += sum(month_days[:month - 1])
|
|
|
|
# calculate the day of the week for the first day of month
|
|
first_day_of_month = (days_since_base_date + base_year_1st_day) % 7
|
|
|
|
# calculate the day of the month
|
|
day_of_month = 1 + (week_of_month - 1) * 7 + (day_of_week - first_day_of_month) % 7
|
|
|
|
if day_of_month > month_days[month - 1]:
|
|
day_of_month -= 7
|
|
|
|
tr = time.mktime((year, month, day_of_month, 0, 0, 0, 0, 0, 0))
|
|
|
|
elif (transition[0] == "J"):
|
|
# 'Jn' format. Counting from 1 to 365, and February 29 is never counted.
|
|
|
|
day_num = int(parts[0][1:])
|
|
if (((((year % 4) == 0) and ((year % 100) != 0)) or (year % 400) == 0) and (day_num > (31 + 28))): # after February 28 in leap years
|
|
day_num += 1
|
|
tr = time.mktime((year,1,1,0,0,0,0,0,0)) + ((day_num - 1) * 86400)
|
|
|
|
else:
|
|
# 'n' format. Counting from zero to 364, or to 365 in leap years.
|
|
|
|
day_num = int(parts[0])
|
|
tr = time.mktime((year,1,1,0,0,0,0,0,0)) + (day_num * 86400)
|
|
|
|
return tr + seconds
|
|
|
|
|
|
def _hours2secs(hours: str):
|
|
"""
|
|
Convert hours string in seconds
|
|
|
|
Parameters:
|
|
hours (str): Hours in format 00[:00][:00]
|
|
|
|
Returns:
|
|
int: seconds
|
|
"""
|
|
seconds = 0
|
|
|
|
hours_parts = hours.split(':')
|
|
|
|
if (len(hours_parts)>0):
|
|
seconds = int(hours_parts[0]) * 3600
|
|
if (len(hours_parts)>1):
|
|
seconds += int(hours_parts[1]) * 60
|
|
if (len(hours_parts)>2):
|
|
seconds += int(hours_parts[2])
|
|
|
|
return seconds
|