home
erste Version am 28.05.2023
letzte Änderung am 29.05.2023

Spielereien mit Zeitzonen


Beruflich muss ich mich gerade mit Zeitzonen befassen.
Da ich das zwar einerseits verwirrend, aber andererseits auch interessant finde, bastel ich (mangels besserer Ideen für Privat-Projekte) eben mal ein bisschen damit rum.
Eigentlich könnten mir Zeitzonen egal sein, weil mein gesamtes Backend (inklusive Datenbank) in bzw. mit UTC läuft - und nur zum Endkunden hin (gemäß dessen jeweiliger Zeitzone) umgewandelt wird.
Naja... aber eben auch nur eigentlich. Schließlich gibt es an diversen Orten sowas wie die Zeitumstellung auf Sommerzeit.
Und wenn dann eine bestimmte Backend-Funktionalität zu einer vom Kunden in Ortszeit vorgegebenen Zeit erfolgen soll, dann muss vom Backend die dortig ggf. gültige Sommerzeit berücksichtigt werden - um es ganzjährig immer zur eingestellen Zeit auszuführen.



Mein Einstieg ins Thema war erstmal das datetime-Modul von Python.
Schnell wurde klar, dass es zusätzlich noch das pytz-Modul braucht. Und hier die Beispielsammlung dazu ... Referenz will man das wohl eher nicht nennen.

Gemäß
>>> import pytz
>>> pytz.__version__
'2023.3'
>>> len(pytz.all_timezones)
596
>>> len(pytz.common_timezones)
433

kennt das (derzeit aktuelle 2023.3'er) Modul also 596 Zeitzonen, 433 davon sollte man voraussichtlich berücksichtigen.

Mit dem Dreizeiler hier bekommt man das Delta:
>>> for tz in pytz.all_timezones:
...    if tz not in pytz.common_timezones:
...       print(tz)
...

Mal schauen, ob common reicht.

Nun stellt sich erstmal die Frage, ob der Wechsel zwischen Winter- und Sommerzeit weltweit immer am selben Datum erfolgt. Laut dem hier offenbar nicht.
Auch muss man den ersten Satz dieses Wikipedia-Artikels (bzw. das "meist" darin) wahrscheinlich so deuten, dass nicht immer um eine Stunde umgestellt wird.
Oh wie grauenvoll.

Dann wird mal das erste Test-Script gebaut:
#! /usr/bin/env python3
# -*- coding: utf-8 -

import sys
import datetime
import pytz

# ----------------------------------------------------------------------
# Liefert 1, wenn für einen Zeitstempel "dt" (mit Zeitzonen-Infos)
# gerade Sommerzeit herrscht. Sonst 0.
def localizedTimestampHasDst(dt):
return dt.timetuple()[8]


# ----------------------------------------------------------------------
# Liefert für einen Zeitstempel aus der Zeitzone "zn" den entsprechenden
# Wert in UTC.
def timestampFromTzToUtc(dt, zn):
return pytz.timezone(zn).localize(dt).astimezone(pytz.timezone('UTC'))


# ----------------------------------------------------------------------
# Liefert für einen Zeitstempel in UTC den entsprechenden Wert in der
# Zeitzone "zn"
def timestampFromUtcToTz(dt, zn):
return pytz.timezone('UTC').localize(dt).astimezone(pytz.timezone(zn))


# ----------------------------------------------------------------------
#
def timezones_std_dst():
tzd = dict()
for tzs in pytz.common_timezones:
tz = pytz.timezone(tzs)
std, dst = None, None
for month in range(1, 13):
dt = datetime.datetime(2023, month, 1, 12, 0, 0)
dt_tz = timestampFromUtcToTz(dt, tzs)
l_dt_ts = localizedTimestampHasDst(dt_tz)
tzv = dt_tz.utcoffset().total_seconds()/60/60
if std is None and l_dt_ts == 0:
std = tzv
stdn = dt_tz.tzname()
if dst is None and l_dt_ts == 1:
dst = tzv
dstn = dt_tz.tzname()
if dst is not None and std is not None:
break
if dst is None:
dst = std
dstn = stdn
tzd.update({tz: { "std": std, "dst": dst,
"stdn": stdn, "dstn": dstn } })
return tzd


# ----------------------------------------------------------------------
#
def find_dst_std_date():
for tz in ("Europe/Berlin", "Atlantic/Bermuda", "UTC", "Canada/Newfoundland", "Australia/Lord_Howe", "Europe/Dublin", "Africa/Casablanca"):
#for tz in pytz.common_timezones:
print("\n", tz)
dt = dt2 = datetime.datetime(2023, 1, 1, 0, 0)

for h in range(6*24*365):
dtz = timestampFromUtcToTz(dt, tz)
dt_is_dst = localizedTimestampHasDst(dtz)
#dt2 += datetime.timedelta(hours=1)
dt2 += datetime.timedelta(minutes=10)
dt2z = timestampFromUtcToTz(dt2, tz)
dt2_is_dst = localizedTimestampHasDst(dt2z)
if dt_is_dst != dt2_is_dst:
print(dt, dt_is_dst, dtz)
print(dt2, dt2_is_dst, dt2z, flush=True)
dt = dt2


# ----------------------------------------------------------------------
# Tests
if __name__ == "__main__":
tzd = timezones_std_dst()
for tz, sd in tzd.items():
ss = str(tz).split("/", 1)
if len(ss) > 1:
continent, city = ss
else:
continent, city = ss[0], ""
if (sd["dst"]-sd["std"]) not in (0, 1.0):
print("%20s / %-25s | %6s = %6.2f | %6s = %6.2f - %3.1f !!!!"%(continent, city, sd["stdn"], sd["std"], sd["dstn"], sd["dst"], sd["dst"]-sd["std"]))
else:
print("%20s / %-25s | %6s = %6.2f | %6s = %6.2f - %3.1f"%(continent, city, sd["stdn"], sd["std"], sd["dstn"], sd["dst"], sd["dst"]-sd["std"]))

find_dst_std_date()


Das liefert sowas hier:
dede@c12:~/tmp/tz> ./test1.py
              Africa / Abidjan                   |    GMT =   0.00 |    GMT =   0.00 - 0.0
[....]
              Africa / Cairo                     |    EET =   2.00 |   EEST =   3.00 - 1.0
              Africa / Casablanca                |    +01 =   1.00 |    +00 =   0.00 - -1.0 !!!!
              Africa / Ceuta                     |    CET =   1.00 |   CEST =   2.00 - 1.0
[....]
             America / Asuncion                  |    -04 =  -4.00 |    -03 =  -3.00 - 1.0
             America / Atikokan                  |    EST =  -5.00 |    EST =  -5.00 - 0.0
[....]
             America / Halifax                   |    AST =  -4.00 |    ADT =  -3.00 - 1.0
[....]
             America / Vancouver                 |    PST =  -8.00 |    PDT =  -7.00 - 1.0
[....]
          Antarctica / Troll                     |    +00 =   0.00 |    +02 =   2.00 - 2.0 !!!!
[....]
                Asia / Kabul                     |  +0430 =   4.50 |  +0430 =   4.50 - 0.0
                Asia / Kamchatka                 |    +12 =  12.00 |    +12 =  12.00 - 0.0
                Asia / Karachi                   |    PKT =   5.00 |    PKT =   5.00 - 0.0
                Asia / Kathmandu                 |  +0545 =   5.75 |  +0545 =   5.75 - 0.0
[....]
            Atlantic / Bermuda                   |    AST =  -4.00 |    ADT =  -3.00 - 1.0
[....]
           Australia / Lord_Howe                 |  +1030 =  10.50 |    +11 =  11.00 - 0.5 !!!!
[....]
              Canada / Newfoundland              |    NST =  -3.50 |    NDT =  -2.50 - 1.0
[....]
              Europe / Berlin                    |    CET =   1.00 |   CEST =   2.00 - 1.0
[....]
              Europe / Dublin                    |    IST =   1.00 |    GMT =   0.00 - -1.0 !!!!
              Europe / Gibraltar                 |    CET =   1.00 |   CEST =   2.00 - 1.0
              Europe / Guernsey                  |    GMT =   0.00 |    BST =   1.00 - 1.0
              Europe / Helsinki                  |    EET =   2.00 |   EEST =   3.00 - 1.0
[....]
              Europe / Madrid                    |    CET =   1.00 |   CEST =   2.00 - 1.0
[....]
             Pacific / Auckland                  |   NZST =  12.00 |   NZDT =  13.00 - 1.0
             Pacific / Bougainville              |    +11 =  11.00 |    +11 =  11.00 - 0.0
             Pacific / Chatham                   |  +1245 =  12.75 |  +1345 =  13.75 - 1.0
[....]
                 UTC /                           |    UTC =   0.00 |    UTC =   0.00 - 0.0

 Europe/Berlin
2023-03-26 00:50:00 0 2023-03-26 01:50:00+01:00
2023-03-26 01:00:00 1 2023-03-26 03:00:00+02:00
2023-10-29 00:50:00 1 2023-10-29 02:50:00+02:00
2023-10-29 01:00:00 0 2023-10-29 02:00:00+01:00

 Atlantic/Bermuda
2023-03-12 05:50:00 0 2023-03-12 01:50:00-04:00
2023-03-12 06:00:00 1 2023-03-12 03:00:00-03:00
2023-11-05 04:50:00 1 2023-11-05 01:50:00-03:00
2023-11-05 05:00:00 0 2023-11-05 01:00:00-04:00

 UTC

 Canada/Newfoundland
2023-03-12 05:20:00 0 2023-03-12 01:50:00-03:30
2023-03-12 05:30:00 1 2023-03-12 03:00:00-02:30
2023-11-05 04:20:00 1 2023-11-05 01:50:00-02:30
2023-11-05 04:30:00 0 2023-11-05 01:00:00-03:30

 Australia/Lord_Howe
2023-04-01 14:50:00 1 2023-04-02 01:50:00+11:00
2023-04-01 15:00:00 0 2023-04-02 01:30:00+10:30
2023-09-30 15:20:00 0 2023-10-01 01:50:00+10:30
2023-09-30 15:30:00 1 2023-10-01 02:30:00+11:00

 Europe/Dublin
2023-03-26 00:50:00 1 2023-03-26 00:50:00+00:00
2023-03-26 01:00:00 0 2023-03-26 02:00:00+01:00
2023-10-29 00:50:00 0 2023-10-29 01:50:00+01:00
2023-10-29 01:00:00 1 2023-10-29 01:00:00+00:00

 Africa/Casablanca
2023-03-19 01:50:00 0 2023-03-19 02:50:00+01:00
2023-03-19 02:00:00 1 2023-03-19 02:00:00+00:00
2023-04-23 01:50:00 1 2023-04-23 01:50:00+00:00
2023-04-23 02:00:00 0 2023-04-23 03:00:00+01:00

Ganz toll. Also -wie fast schon erwartet- .... völliges Kuddelmuddel.
Im ersten Teil wird für alle üblichen Zeitzonen das Delta zwischen Winterzeit und Sommerzeit ausgegeben, im zweiten Teil wird für einzelne Zeitzonen das Umschalt-Datum im 10-Minuten-Raster gesucht und angezeigt.

In Berlin wurde am 26.03.2023 um 01:00 UTC bzw. 02:00 Ortszeit die Uhr um eine Stunde vorgestellt.
Auf den Bermudas wurde am 12.03.2023 um 06:00 UTC bzw. 02:00 Ortszeit um eine Stunde vorgestellt.
Auf Neufundland auch am 12.03.2023 - jedoch um 05:30 UTC bzw. 02:00 Ortszeit, aber auch eine Stunde vor.
Und auf den Lord-Howe-Inseln haben sie am 02.04.2023 auf Winterzeit umgestellt - aber nur um eine halbe Stunde zurück.
Dublin hat wieder mal Sonderwünsche und am 26.03.2023 für die Winterzeit um eine Stunde vorgestellt.
Casablanca am 23.04. ebenso.

Und wie kann in Gibralta immer exakt die selbe Zeit wie in Berlin gelten?
Das ist doch reine Willkür (aka "politisch motiviert") und kann nix mehr mit den Sonnen-auf/untergangs-Zeiten zu tun haben.....!?



Erstmal vorab als Merker:
MySQL kennt (offenbar optional) eine Funktion CONVERT_TZ(), mit der ein Timestamp von einer Zeitzone in eine andere umgerechnet werden kann.
Mal schauen, wie es diesbezüglich mit dem MySQL von Googles "Cloud SQL" aussieht.... was die Zielplattform ist.



Nun will ich mal begreifen, wann wo Sommerzeit gilt und welche Rolle der Äquator spielt.
Dazu braucht es erstmal ein paar Orte über den Globus verteilt.
1.) (NW) America/Vancouver |    PST =  -8.00 |    PDT =  -7.00 - 1.0
2.) (SW) America/Asuncion |    -04 =  -4.00 |    -03 =  -3.00 - 1.0
3.) (NE) Europe/Helsinki |    EET =   2.00 |   EEST =   3.00 - 1.0
4.) (SE) Pacific/Auckland |   NZST =  12.00 |   NZDT =  13.00 - 1.0

Im Westen ist das Delta zu UTC negativ, im Osten positiv. War klar. Wenig spannend.

Dann Nord / Süd:
 America/Vancouver
2023-03-12 09:50:00 0 2023-03-12 01:50:00-08:00
2023-03-12 10:00:00 1 2023-03-12 03:00:00-07:00
2023-11-05 08:50:00 1 2023-11-05 01:50:00-07:00
2023-11-05 09:00:00 0 2023-11-05 01:00:00-08:00

 America/Asuncion
2023-03-26 02:50:00 1 2023-03-25 23:50:00-03:00
2023-03-26 03:00:00 0 2023-03-25 23:00:00-04:00
2023-10-01 03:50:00 0 2023-09-30 23:50:00-04:00
2023-10-01 04:00:00 1 2023-10-01 01:00:00-03:00

 Europe/Helsinki
2023-03-26 00:50:00 0 2023-03-26 02:50:00+02:00
2023-03-26 01:00:00 1 2023-03-26 04:00:00+03:00
2023-10-29 00:50:00 1 2023-10-29 03:50:00+03:00
2023-10-29 01:00:00 0 2023-10-29 03:00:00+02:00

 Pacific/Auckland
2023-04-01 13:50:00 1 2023-04-02 02:50:00+13:00
2023-04-01 14:00:00 0 2023-04-02 02:00:00+12:00
2023-09-23 13:50:00 0 2023-09-24 01:50:00+12:00
2023-09-23 14:00:00 1 2023-09-24 03:00:00+13:00

Nördlich vom Äquator gilt im Januar die Winterzeit. Zur Umschaltung auf Sommerzeit wird die Zeit vorgestellt (02:00 -> 03:00 bzw. 03:00 -> 04:00).
Südlich vom Äquator gilt im Januar die Sommerzeit. Zur Umschaltung auf Winterzeit wird die Zeit zurückgestellt (00:00 -> 23:00 bzw. 03:00 -> 02:00).

Und wenn ich das jetzt für 2024 laufen lasse, kommt das hier:
 America/Vancouver
2024-03-10 09:50:00 0 2024-03-10 01:50:00-08:00
2024-03-10 10:00:00 1 2024-03-10 03:00:00-07:00
2024-11-03 08:50:00 1 2024-11-03 01:50:00-07:00
2024-11-03 09:00:00 0 2024-11-03 01:00:00-08:00

 America/Asuncion
2024-03-24 02:50:00 1 2024-03-23 23:50:00-03:00
2024-03-24 03:00:00 0 2024-03-23 23:00:00-04:00
2024-10-06 03:50:00 0 2024-10-05 23:50:00-04:00
2024-10-06 04:00:00 1 2024-10-06 01:00:00-03:00

 Europe/Helsinki
2024-03-31 00:50:00 0 2024-03-31 02:50:00+02:00
2024-03-31 01:00:00 1 2024-03-31 04:00:00+03:00
2024-10-27 00:50:00 1 2024-10-27 03:50:00+03:00
2024-10-27 01:00:00 0 2024-10-27 03:00:00+02:00

 Pacific/Auckland
2024-04-06 13:50:00 1 2024-04-07 02:50:00+13:00
2024-04-06 14:00:00 0 2024-04-07 02:00:00+12:00
2024-09-28 13:50:00 0 2024-09-29 01:50:00+12:00
2024-09-28 14:00:00 1 2024-09-29 03:00:00+13:00

Nebenbei ist das der Beweis, dass das Datum des Umschalt-Termins überall jedes Jahr wechselt.

Das hier macht die vermeintliche Regel von eben allerdings wieder kaputt:
 Europe/Dublin
2023-03-26 00:50:00 1 2023-03-26 00:50:00+00:00
2023-03-26 01:00:00 0 2023-03-26 02:00:00+01:00
2023-10-29 00:50:00 0 2023-10-29 01:50:00+01:00
2023-10-29 01:00:00 1 2023-10-29 01:00:00+00:00

 Africa/Casablanca
2023-03-19 01:50:00 0 2023-03-19 02:50:00+01:00
2023-03-19 02:00:00 1 2023-03-19 02:00:00+00:00
2023-04-23 01:50:00 1 2023-04-23 01:50:00+00:00
2023-04-23 02:00:00 0 2023-04-23 03:00:00+01:00

Winter- auf Sommerzeit in Dublin: 02:00 -> 01:00
in Casablanca: 03:00 -> 02:00.
Das kennen wir ja schon von ... siehe zwei Abschnitte höher.



Was habe und brauche ich eigentlich !?
Ich habe:
- ein Datum vom letzten Lauf der Funktion
- eine Wiederholfrequenz in Wochen
- den gewünschten Wochentag
- die gewünschte Zeit (von Ortszeit nach UTC gewandelt) / alternativ könnte hier auch die eingegebene Ortszeit genutzt werden
- die Zeitzone, in der die Zeit gelten soll
- ...und eigentlich auch noch einen Wochen-Offset, um bei z.B. einer vier-Wochen-Frequenz eine der vier Wochen dazwischen vorgeben zu können... hier aber egal... glaube ich
Brauchen tue ich:
- Datum und Zeit des nächsten Laufes in UTC.

Mögliche Werte könnten beispielsweise sein:
- 2023-01-02
- 4
- Montag
- 08:00:00 (gewählt wurde 07:00 Ortszeit, umgewandelt zur Normal- bzw. Winterzeit)
- Europe/Berlin
- 0 (für KW1, KW5, KW9, KW13, ...)

Erster Test (mit der nicht nach UTC gewandelten Zeit):
>>> def timestampFromTzToUtc(dt, zn):
...     return pytz.timezone(zn).localize(dt).astimezone(pytz.timezone('UTC'))
...
>>> def timestampFromUtcToTz(dt, zn):
...      return pytz.timezone('UTC').localize(dt).astimezone(pytz.timezone(zn))
...
>>> db = datetime.datetime(2023, 1, 2)
>>> tb = datetime.time(7, 0)      # eingegebene Ortszeit
>>> w = 4                         # Intervall
>>> zn = [ "Europe/Berlin" ]      # 26.03. / 29.10. |  1 /  2
>>> zn += [ "America/Halifax" ]   # 12.03. / 05.11. | -4 / -3
>>> for z in zn:
...     d, t = db, tb
...     for m in range(14):
...         dt = datetime.datetime.combine(d, t)
...         dt = timestampFromTzToUtc(dt, z).replace(tzinfo=None)
...         print(dt, "   ", timestampFromUtcToTz(dt, z), "   ", z)
...         d += datetime.timedelta(weeks=w)
...     print()
...
2023-01-02 06:00:00     2023-01-02 07:00:00+01:00     Europe/Berlin
2023-01-30 06:00:00     2023-01-30 07:00:00+01:00     Europe/Berlin
2023-02-27 06:00:00     2023-02-27 07:00:00+01:00     Europe/Berlin
2023-03-27 05:00:00     2023-03-27 07:00:00+02:00     Europe/Berlin
2023-04-24 05:00:00     2023-04-24 07:00:00+02:00     Europe/Berlin
2023-05-22 05:00:00     2023-05-22 07:00:00+02:00     Europe/Berlin
2023-06-19 05:00:00     2023-06-19 07:00:00+02:00     Europe/Berlin
2023-07-17 05:00:00     2023-07-17 07:00:00+02:00     Europe/Berlin
2023-08-14 05:00:00     2023-08-14 07:00:00+02:00     Europe/Berlin
2023-09-11 05:00:00     2023-09-11 07:00:00+02:00     Europe/Berlin
2023-10-09 05:00:00     2023-10-09 07:00:00+02:00     Europe/Berlin
2023-11-06 06:00:00     2023-11-06 07:00:00+01:00     Europe/Berlin
2023-12-04 06:00:00     2023-12-04 07:00:00+01:00     Europe/Berlin
2024-01-01 06:00:00     2024-01-01 07:00:00+01:00     Europe/Berlin

2023-01-02 11:00:00     2023-01-02 07:00:00-04:00     America/Halifax
2023-01-30 11:00:00     2023-01-30 07:00:00-04:00     America/Halifax
2023-02-27 11:00:00     2023-02-27 07:00:00-04:00     America/Halifax
2023-03-27 10:00:00     2023-03-27 07:00:00-03:00     America/Halifax
2023-04-24 10:00:00     2023-04-24 07:00:00-03:00     America/Halifax
2023-05-22 10:00:00     2023-05-22 07:00:00-03:00     America/Halifax
2023-06-19 10:00:00     2023-06-19 07:00:00-03:00     America/Halifax
2023-07-17 10:00:00     2023-07-17 07:00:00-03:00     America/Halifax
2023-08-14 10:00:00     2023-08-14 07:00:00-03:00     America/Halifax
2023-09-11 10:00:00     2023-09-11 07:00:00-03:00     America/Halifax
2023-10-09 10:00:00     2023-10-09 07:00:00-03:00     America/Halifax
2023-11-06 11:00:00     2023-11-06 07:00:00-04:00     America/Halifax
2023-12-04 11:00:00     2023-12-04 07:00:00-04:00     America/Halifax
2024-01-01 11:00:00     2024-01-01 07:00:00-04:00     America/Halifax
Das sieht ja eigentlich schon ganz gut aus. Alle Termine liegen auf einem Montag.

Nun mal ein bischen böser:
>>> db = datetime.datetime(2023, 1, 2)
>>> tb = datetime.time(0, 30)      # eingegebene Ortszeit
>>> w = 4                         # Intervall
>>> zn = [ "Europe/Berlin" ]      # 26.03. / 29.10. | 1 /  2
>>> #zn += [ "America/Halifax" ]   # 12.03. / 05.11. | -4 / -3
... for z in zn:
...     d, t = db, tb
...     for m in range(1):
...         dt = datetime.datetime.combine(d, t)
...         dt = timestampFromTzToUtc(dt, z).replace(tzinfo=None)
...        
print(dt, "   ", timestampFromUtcToTz(dt, z), "   ", z)
...         d += datetime.timedelta(weeks=w)
...     print()
...
2023-01-01 23:30:00     2023-01-02 00:30:00+01:00     Europe/Berlin
Und schon passt der Wochentag nicht mehr.
Der 01.01.2023 war ein Sonntag - nicht Montag. Genau das war nämlich meine Befürchtung.
Andererseits ist in der gewählten Zeitzone zu der Zeit sehr wohl schon Montag.

Sollte es tatsächlich reichen, die Uhrzeit in (der vom Kunden eingestellten) Ortszeit in der Tabelle abzulegen!?
Schließlich kann man sich laut obiger Übersicht nicht darauf verlassen, dass am 01. Januar überall immer Winterzeit gilt. Blöde Süd-Halbkugel ... und Dublin ... und einige mehr.
Aber warum eigentlich Winter- alias Normalzeit?
Ebensogut könnte man definieren, dass die gespeicherte Zeit in UTC der eingegebenen Ortszeit am 1. Januar entspricht. Damit wäre es in UTC abgelegt und selbst die lästigen Dogmatiker wären zufrieden.
Etwa so:
>>> t = datetime.time(7, 0)      # eingegebene Ortszeit
>>> for z in ("America/Vancouver", "America/Asuncion", "Europe/Helsinki", "Pacific/Auckland"):
...     dt = datetime.datetime.combine(datetime.datetime(2020, 1, 1), t)
...     t_utc = timestampFromTzToUtc(dt, z).replace(tzinfo=None).time()
...     print("UTC", t_utc, z)
...     # -----
...     dt = datetime.datetime.combine(datetime.datetime(2020, 1, 1), t_utc)
...     t_loc = timestampFromUtcToTz(dt, z).replace(tzinfo=None).time()
...     print("LOC", t_loc, z, "\n")
...
UTC 15:00:00 America/Vancouver
LOC 07:00:00 America/Vancouver

UTC 10:00:00 America/Asuncion
LOC 07:00:00 America/Asuncion

UTC 05:00:00 Europe/Helsinki
LOC 07:00:00 Europe/Helsinki

UTC 18:00:00 Pacific/Auckland
LOC 07:00:00 Pacific/Auckland 

Wobei ein TIME aus MySQL bei Python als datetime.timedelta() ankommt.
Also braucht es noch (für den Hin- und Rückweg):
>>> td = datetime.timedelta(0, 25200)           # so kommt es aus der DB für 07:00:00
>>> tm = (datetime.datetime.min + td).time()    # datetime.timedelta nach datetime.time wandeln
>>> tm
datetime.time(7, 0)
>>> print(tm, type(tm))
07:00:00 <class 'datetime.time'>
>>> td = datetime.datetime.combine(datetime.date.min, tm) - datetime.datetime.min
>>> td
datetime.timedelta(0, 25200)
>>> print(td, type(td))
7:00:00 <class 'datetime.timedelta'>



Zusammenfassung der bisherigen Erkenntnisse:
1.) es gilt ein jährlich wechselndes Datum für den Umschalt-Termin je Zeitzone.
2.) es wird nicht überall um genau 0h oder 1h umgestellt. Auch 0.5h oder 2h kommen sporadisch vor.
3.) am 1. Januar kann pro Zeitzone entweder Sommerzeit oder Winterzeit gelten.
4.) beim Umschalten auf Sommerzeit wird die Uhr meist (aber nicht immer) vorgestellt.

Daraus folgt: ich sehe schwarz für einen einzelnen Query, der mir die "jetzt zu startenden Sätze" liefert.
Bei der vorigen Version war das noch möglich. Da gab es aber auch nur ein paar ausgewählte Zeitzonen in Europa zur Auswahl.



Die Berechnung soll final nicht vom Zeitpunkt des letzten Laufes abhängen.
Das Zeit-Raster kommt durch die anderen Parameter zustande und muß lediglich zeitlich jünger als der letzte Lauf sein.

Dafür braucht es noch diese zwei Funktionen:
>>> # Python datetime.weekday() nach MySQL.DAYOFWEEK() wandeln.
... # Für MySQL.DAYOFWEEK() gilt:  1=So, 2=Mo, 3=Di, ..., 7=Sa
... # Für datetime.weekday() gilt: 0=Mo, 1=Di, 3=Mi, ..., 6=So
... wd_conv_py_to_mysql = { 0: 2,   # Mo
...                         1: 3,   # Di
...                         2: 4,   # Mi
...                         3: 5,   # Do
...                         4: 6,   # Fr
...                         5: 7,   # Sa
...                         6: 1  } # So
>>>
>>> # ----------------------------------------------------------------------
... # Liefert den Wochentags-Index von dt in MySQL-Notation.
... def week_day(dt):
...     return wd_conv_py_to_mysql[dt.weekday()]
...
>>> # ----------------------------------------------------------------------
... # Liefert die KW von dt gemäß ISO.
... def week_nr(dt):
...     return dt.isocalendar()[1]
...
>>> week_day(datetime.datetime(2023, 5, 29, 0)) # Montag
2
>>> week_nr(datetime.datetime(2023, 1, 1, 0))   # zählt bzgl. KW noch zu 2022
52
>>> week_nr(datetime.datetime(2023, 1, 2, 0))   # KW1
1
>>> week_nr(datetime.datetime(2023, 5, 29, 0))  # KW22
22

Mal schauen, ob ich mit diesen Erkenntnissen morgen mein Scheduler-Script fertigstellen kann....