Skip to content

Commit 35a76a4

Browse files
authored
🕙 Use unix epoch to generate ticks (#189)
* Use Unix epoch to calculate datetime ticks * Rename a variable for clarity and add a needed test
1 parent e5c6633 commit 35a76a4

3 files changed

Lines changed: 54 additions & 10 deletions

File tree

src/electionguard/ballot.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ def make_ciphertext_ballot(
850850
contest_hashes = [contest.crypto_hash for contest in contests]
851851
contest_hash = hash_elems(object_id, description_hash, *contest_hashes)
852852

853-
timestamp = to_ticks(datetime.utcnow()) if timestamp is None else timestamp
853+
timestamp = to_ticks(datetime.now()) if timestamp is None else timestamp
854854
if previous_tracking_hash is None:
855855
previous_tracking_hash = description_hash
856856
if tracking_hash is None:

src/electionguard/utils.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
from datetime import datetime, timezone
22
from os import mkdir, path
33
from re import sub
44
from typing import Callable, Optional, TypeVar
@@ -66,14 +66,18 @@ def flatmap_optional(optional: Optional[T], mapper: Callable[[T], U]) -> Optiona
6666

6767
def to_ticks(date_time: datetime) -> int:
6868
"""
69-
Return the number of ticks for a date time
69+
Return the number of ticks for a date time.
70+
Ticks are defined here as number of seconds since the unix epoch (00:00:00 UTC on 1 January 1970)
7071
:param date_time: Date time to convert
7172
:return: number of ticks
7273
"""
73-
t0 = datetime(1, 1, 1)
74-
seconds = int((date_time - t0).total_seconds())
75-
ticks = seconds * 10 ** 7
76-
return ticks
74+
75+
ticks = (
76+
date_time.timestamp()
77+
if date_time.tzinfo
78+
else date_time.astimezone(timezone.utc).timestamp()
79+
)
80+
return int(ticks)
7781

7882

7983
def space_between_capitals(base: str) -> str:

tests/test_utils.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,53 @@
11
from unittest import TestCase
2-
from datetime import datetime
2+
from datetime import datetime, timedelta, timezone
33

44
from electionguard.utils import to_ticks
55

66

77
class TestUtils(TestCase):
8-
def test_conversion_to_ticks(self):
8+
def test_conversion_to_ticks_from_utc(self):
99
# Act
10-
ticks = to_ticks(datetime.utcnow())
10+
ticks = to_ticks(datetime.now(timezone.utc))
1111

1212
self.assertIsNotNone(ticks)
1313
self.assertGreater(ticks, 0)
14+
15+
def test_conversion_to_ticks_from_local(self):
16+
# Act
17+
ticks = to_ticks(datetime.now())
18+
19+
self.assertIsNotNone(ticks)
20+
self.assertGreater(ticks, 0)
21+
22+
def test_conversion_to_ticks_with_tz(self):
23+
# Arrange
24+
now = datetime.now()
25+
now_with_tz = (now).astimezone()
26+
now_utc_with_tz = now_with_tz.astimezone(timezone.utc)
27+
28+
# Act
29+
ticks_now = to_ticks(now)
30+
ticks_local = to_ticks(now_with_tz)
31+
ticks_utc = to_ticks(now_utc_with_tz)
32+
33+
# Assert
34+
self.assertIsNotNone(ticks_now)
35+
self.assertIsNotNone(ticks_local)
36+
self.assertIsNotNone(ticks_utc)
37+
38+
# Ensure all three tick values are the same
39+
unique_ticks = set([ticks_now, ticks_local, ticks_utc])
40+
self.assertEqual(1, len(unique_ticks))
41+
self.assertTrue(ticks_now in unique_ticks)
42+
43+
def test_conversion_to_ticks_produces_valid_epoch(self):
44+
# Arrange
45+
now = datetime.now()
46+
47+
# Act
48+
ticks_now = to_ticks(now)
49+
now_from_ticks = datetime.fromtimestamp(ticks_now)
50+
51+
# Assert
52+
# Values below seconds are dropped from the epoch
53+
self.assertEqual(now.replace(microsecond=0), now_from_ticks)

0 commit comments

Comments
 (0)