Skip to content

Instantly share code, notes, and snippets.

@eevmanu
Last active March 5, 2024 15:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save eevmanu/fb2261861c83a0be725fabc43dd1a27c to your computer and use it in GitHub Desktop.
Save eevmanu/fb2261861c83a0be725fabc43dd1a27c to your computer and use it in GitHub Desktop.
Get Epoch Timestamp (Timezone-Aware) at Start of Specific Date
# some alternatives to get date or datetime
# in order to get epoch seconds of the beginning of the choosen date
# --------------------------------------------------------------------------------
# alternative 1
# --------------------------------------------------------------------------------
from datetime import datetime
from zoneinfo import ZoneInfo
datetime.now(tz=ZoneInfo('localtime'))
# requires an extra std package `zoneinfo`
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# alternative 2
# --------------------------------------------------------------------------------
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
datetime.now(tz=timezone.utc).astimezone(ZoneInfo('localtime'))
# required an extra std package `zoneinfo` and
# set timezone of naive datetime object and
# move timezone of aware datetime object
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# alternative 3
# --------------------------------------------------------------------------------
from datetime import datetime
datetime.now()
# is a naive datetime object
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# alternative 4
# --------------------------------------------------------------------------------
from datetime import date
from time import mktime
int(mktime(date.today().timetuple()))
# is using a naive datetime object
# `timetuple(...)` assume local timezone on naive date
# a bit dangerous assumption 👀
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# alternative 5
# --------------------------------------------------------------------------------
from datetime import datetime, date
from zoneinfo import ZoneInfo
tmp = date.today()
datetime(year=tmp.year, month=tmp.month, day=tmp.day, tzinfo=ZoneInfo('localtime'))
# requires an extra std package `zoneinfo` and
# create a date object and
# set timezone of naive datetime object
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# alternative 6
# --------------------------------------------------------------------------------
# time.time() > time.time_ns()
# but by not so much
# but if you want integer
# int(time.time()) < (time.time_ns() // 1_000_000_000)
from time import time_ns
epoch_nanoseconds = time_ns()
epoch_nanoseconds // 1_000_000_000 - (epoch_nanoseconds // 1_000_000_000 % 86_400) + 18000
# FASTEST way to get epoch seconds at the beginning of a day
# but won't consider since it works by doing several assumptions
# --------------------------------------------------------------------------------

These are the situations tested with both scenarios

test 1: using perf_counter

def function_1():
    from time import perf_counter

    from datetime import datetime
    t1 = datetime.now().astimezone()
    min_tmp = 1_000_000_000
    for _ in range(10_000):
        tmp1 = perf_counter()
        int(t1.replace(hour=0,minute=0,second=0).timestamp())
        tmp2 = perf_counter()
        min_tmp = min(min_tmp, tmp2-tmp1)
    print(f"elapsed {min_tmp*1E6} microseconds (µs)")

    from datetime import datetime
    from time import mktime
    min_tmp = 1_000_000_000
    for _ in range(10_000):
        t2 = datetime.now().astimezone()
        tmp1 = perf_counter()
        int(mktime(t2.date().timetuple()))
        tmp2 = perf_counter()
        min_tmp = min(min_tmp, tmp2-tmp1)
    print(f"elapsed {min_tmp*1E6} microseconds (µs)")

function_1()

results

elapsed 1.401989720761776 microseconds (µs)
elapsed 1.8440186977386475 microseconds (µs)

test 2: using perf_counter_ns

def function_2():
    from time import perf_counter_ns

    from datetime import datetime
    t1 = datetime.now().astimezone()
    min_tmp = 1_000_000_000
    for _ in range(10_000):
        tmp1 = perf_counter_ns()
        int(t1.replace(hour=0,minute=0,second=0).timestamp())
        tmp2 = perf_counter_ns()
        min_tmp = min(min_tmp, tmp2-tmp1)
    print(f"elapsed {min_tmp/1E3} microseconds (µs)")

    from datetime import datetime
    from time import mktime
    min_tmp = 1_000_000_000
    for _ in range(10_000):
        t2 = datetime.now().astimezone()
        tmp1 = perf_counter_ns()
        int(mktime(t2.date().timetuple()))
        tmp2 = perf_counter_ns()
        min_tmp = min(min_tmp, tmp2-tmp1)
    print(f"elapsed {min_tmp/1E3} microseconds (µs)")

function_2()

results

elapsed 1.402 microseconds (µs)
elapsed 1.844 microseconds (µs)

test 3: using timeit on python code

def function_3():
    from timeit import timeit

    setup = (
        "from datetime import datetime;"
        "t1 = datetime.now().astimezone()"
    )

    statement = (
        "int(t1.replace(hour=0,minute=0,second=0).timestamp())"
    )

    elapsed = timeit(statement, setup)
    print(f"elapsed {elapsed}")
    # print(f"elapsed {elapsed} microseconds (µs)")

    setup = (
        "from datetime import datetime;"
        "from time import mktime;"
        "t2 = datetime.now().astimezone()"
    )

    statement = (
        "int(mktime(t2.date().timetuple()))"
    )

    elapsed = timeit(statement, setup)
    print(f"elapsed {elapsed}")
    # print(f"elapsed {elapsed} microseconds (µs)")
    
function_3()

results

elapsed 1.425439064973034
elapsed 1.778326635947451

test 4: using timeit call as cli command

$ python -m timeit -s "from datetime import datetime;t1 = datetime.now().astimezone()" -r 10 -n 1000000 "int(t1.replace(hour=0,minute=0,second=0).timestamp())"
1000000 loops, best of 10: 1.39 usec per loop

$ python -m timeit -s "from datetime import datetime;from time import mktime;t2 = datetime.now().astimezone()" -r 10 -n 1000000 "int(mktime(t2.date().timetuple()))"
1000000 loops, best of 10: 1.77 usec per loop

test 5: using timeit + perf_counter_ns as default timer on python code

def function_5():
    from timeit import timeit
    from time import perf_counter_ns

    setup = (
        "from datetime import datetime;"
        "t1 = datetime.now().astimezone()"
    )

    statement = (
        "int(t1.replace(hour=0,minute=0,second=0).timestamp())"
    )

    elapsed = timeit(statement, setup, timer=perf_counter_ns)
    print(f"elapsed {elapsed*1E-9}")

    setup = (
        "from datetime import datetime;"
        "from time import mktime;"
        "t2 = datetime.now().astimezone()"
    )

    statement = (
        "int(mktime(t2.date().timetuple()))"
    )

    elapsed = timeit(statement, setup, timer=perf_counter_ns)
    print(f"elapsed {elapsed*1E-9}")

function_5()

results

elapsed 1.392349128
elapsed 1.77519522

test 6: using timeit + perf_counter_ns as default timer on cli command

not possible since there is no cli option to change default timer when calling timeit as module

test 7: using different python versions

all previous steps where executed in a python env with version 3.11.8 reason why I'm not including that version here

$ python3.12.2 -m timeit -s "from datetime import datetime;t1 = datetime.now().astimezone()" -r 10 -n 1000000 "int(t1.replace(hour=0,minute=0,second=0).timestamp())"
1000000 loops, best of 10: 1.63 usec per loop
$ python3.12.2 -m timeit -s "from datetime import datetime;from time import mktime;t2 = datetime.now().astimezone()" -r 10 -n 1000000 "int(mktime(t2.date().timetuple()))"
1000000 loops, best of 10: 2.02 usec per loop

$ python3.10.13 -m timeit -s "from datetime import datetime;t1 = datetime.now().astimezone()" -r 10 -n 1000000 "int(t1.replace(hour=0,minute=0,second=0).timestamp())"
1000000 loops, best of 10: 1.48 usec per loop
$ python3.10.13 -m timeit -s "from datetime import datetime;from time import mktime;t2 = datetime.now().astimezone()" -r 10 -n 1000000 "int(mktime(t2.date().timetuple()))"
1000000 loops, best of 10: 1.93 usec per loop

$ python3.9.18 -m timeit -s "from datetime import datetime;t1 = datetime.now().astimezone()" -r 10 -n 1000000 "int(t1.replace(hour=0,minute=0,second=0).timestamp())"
1000000 loops, best of 10: 1.47 usec per loop
$ python3.9.18 -m timeit -s "from datetime import datetime;from time import mktime;t2 = datetime.now().astimezone()" -r 10 -n 1000000 "int(mktime(t2.date().timetuple()))"
1000000 loops, best of 10: 1.93 usec per loop
# using `.astimezone()` in both since prefer `aware` objects instead of `naive`
# --------------------------------------------------------------------------------
# scenario 1 - `.replace()` + `.timestamp()`
# --------------------------------------------------------------------------------
from datetime import datetime
t1 = datetime.now().astimezone()
int(t1.replace(hour=0,minute=0,second=0).timestamp())
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# scenario 2 - `.timetuple()` + `.mktime()`
# --------------------------------------------------------------------------------
from datetime import datetime
from time import mktime
t2 = datetime.now().astimezone()
int(mktime(t2.date().timetuple()))
# --------------------------------------------------------------------------------
# results
# way 1
$ python3.12.2 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "int((datetime.now(tz=timezone.utc).replace(tzinfo=None,microsecond=0) -datetime.now().replace(microsecond=0)).total_seconds())"
1000000 loops, best of 10: 2.59 usec per loop
$ python3.11.8 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "int((datetime.now(tz=timezone.utc).replace(tzinfo=None,microsecond=0) -datetime.now().replace(microsecond=0)).total_seconds())"
1000000 loops, best of 10: 2.35 usec per loop
$ python3.10.13 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "int((datetime.now(tz=timezone.utc).replace(tzinfo=None,microsecond=0) -datetime.now().replace(microsecond=0)).total_seconds())"
1000000 loops, best of 10: 2.46 usec per loop
$ python3.9.18 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "int((datetime.now(tz=timezone.utc).replace(tzinfo=None,microsecond=0) -datetime.now().replace(microsecond=0)).total_seconds())"
1000000 loops, best of 10: 2.47 usec per loop
# way 2
$ python3.12.2 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "(datetime.now(tz=timezone.utc).replace(tzinfo=None,second=0,microsecond=0) -datetime.now().replace(second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 2.44 usec per loop
$ python3.11.8 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "(datetime.now(tz=timezone.utc).replace(tzinfo=None,second=0,microsecond=0) -datetime.now().replace(second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 2.25 usec per loop
$ python3.10.13 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "(datetime.now(tz=timezone.utc).replace(tzinfo=None,second=0,microsecond=0) -datetime.now().replace(second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 2.3 usec per loop
$ python3.9.18 -m timeit -s "from datetime import datetime, timezone" -r 10 -n 1000000 "(datetime.now(tz=timezone.utc).replace(tzinfo=None,second=0,microsecond=0) -datetime.now().replace(second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 2.32 usec per loop
# way 3
$ python3.12.2 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now(tz=timezone.utc); (t1.replace(tzinfo=None,second=0,microsecond=0) -t1.astimezone(ZoneInfo('localtime')).replace(tzinfo=None, second=0, microsecond=0)).seconds"
1000000 loops, best of 10: 2.76 usec per loop
$ python3.11.8 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now(tz=timezone.utc); (t1.replace(tzinfo=None,second=0,microsecond=0) -t1.astimezone(ZoneInfo('localtime')).replace(tzinfo=None, second=0, microsecond=0)).seconds"
1000000 loops, best of 10: 2.61 usec per loop
$ python3.10.13 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now(tz=timezone.utc); (t1.replace(tzinfo=None,second=0,microsecond=0) -t1.astimezone(ZoneInfo('localtime')).replace(tzinfo=None, second=0, microsecond=0)).seconds"
1000000 loops, best of 10: 2.59 usec per loop
$ python3.9.18 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now(tz=timezone.utc); (t1.replace(tzinfo=None,second=0,microsecond=0) -t1.astimezone(ZoneInfo('localtime')).replace(tzinfo=None, second=0, microsecond=0)).seconds"
1000000 loops, best of 10: 2.53 usec per loop
# way 4
$ python3.12.2 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now();(t1.astimezone(ZoneInfo('UTC')).replace(tzinfo=None,second=0,microsecond=0) -t1.replace(tzinfo=None,second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 3.48 usec per loop
$ python3.11.8 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now();(t1.astimezone(ZoneInfo('UTC')).replace(tzinfo=None,second=0,microsecond=0) -t1.replace(tzinfo=None,second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 2.97 usec per loop
$ python3.10.13 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now();(t1.astimezone(ZoneInfo('UTC')).replace(tzinfo=None,second=0,microsecond=0) -t1.replace(tzinfo=None,second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 3 usec per loop
$ python3.9.18 -m timeit -s "from datetime import datetime, timezone; from zoneinfo import ZoneInfo" -r 10 -n 1000000 "t1 = datetime.now();(t1.astimezone(ZoneInfo('UTC')).replace(tzinfo=None,second=0,microsecond=0) -t1.replace(tzinfo=None,second=0,microsecond=0)).seconds"
1000000 loops, best of 10: 2.93 usec per loop
# ways to find diff between local timezone and UTC timezone in seconds
# --------------------------------------------------------------------------------
# way 1
# --------------------------------------------------------------------------------
from datetime import datetime, timezone
int(
(
datetime.now(tz=timezone.utc).replace(tzinfo=None,microsecond=0) -
datetime.now().replace(microsecond=0)
).total_seconds()
)
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# way 2
# --------------------------------------------------------------------------------
from datetime import datetime, timezone
(
datetime.now(tz=timezone.utc).replace(tzinfo=None,second=0,microsecond=0) -
datetime.now().replace(second=0,microsecond=0)
).seconds
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# way 3
# --------------------------------------------------------------------------------
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
t1 = datetime.now(tz=timezone.utc)
(
t1.replace(tzinfo=None,second=0,microsecond=0) -
t1.astimezone(ZoneInfo('localtime')).replace(tzinfo=None, second=0, microsecond=0)
).seconds
# --------------------------------------------------------------------------------
# --------------------------------------------------------------------------------
# way 4
# --------------------------------------------------------------------------------
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
t1 = datetime.now()
(
t1.astimezone(ZoneInfo('UTC')).replace(tzinfo=None,second=0,microsecond=0) -
t1.replace(tzinfo=None,second=0,microsecond=0)
).seconds
# --------------------------------------------------------------------------------
# conclusion:
# if is okay to assume your timezone won't change
# better use a fixed hardcoded value in seconds (my case 18000)
# on how much shift between your timezone and UTC is in seconds
# instead of trying to calculate it
# neither of these 4 ways will reduce the overload they include
# in order to consider them instead of using `.replace(...)` or
# `.date()` in scenario t1 or t2 to consider replacing them
@eevmanu
Copy link
Author

eevmanu commented Mar 3, 2024

order to read the files:

  1. get-date.alternatives.py - in this file I list some of the ways that I found through my research in order to get the actual date
  2. get-epoch-start-day.py - in this file are the final 2 scenarios that I found to get the epoch seconds of the beginning of a selected date
  3. get-epoch-start-day.bench.md - in this file I walkthrough all the test I made in order to confirm which of the 2 scenarios perform better
  4. local-timezone-to-utc-seconds.py - in this file I list some of the ways that I found to get the difference between my local timezone and UTC in seconds
  5. local-timezone-to-utc-seconds.bench.sh - in this file I did a benchmark of the ways found in file 4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment