Skip to content

Commit 42e626c

Browse files
committed
fix: make all datetimes timezone aware
1 parent 3dc49c6 commit 42e626c

File tree

4 files changed

+29
-28
lines changed

4 files changed

+29
-28
lines changed

src/elections/tables.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ class Election(Base):
2424
slug: Mapped[str] = mapped_column(String(MAX_ELECTION_SLUG), primary_key=True)
2525
name: Mapped[str] = mapped_column(String(MAX_ELECTION_NAME), nullable=False)
2626
type: Mapped[str] = mapped_column(String(64), default="general_election")
27-
datetime_start_nominations: Mapped[datetime] = mapped_column(DateTime(), nullable=False)
28-
datetime_start_voting: Mapped[datetime] = mapped_column(DateTime(), nullable=False)
29-
datetime_end_voting: Mapped[datetime] = mapped_column(DateTime(), nullable=False)
27+
datetime_start_nominations: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
28+
datetime_start_voting: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
29+
datetime_end_voting: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
3030

3131
# a comma-separated string of positions which must be elements of OfficerPosition
3232
# By giving it the type `StringList`, the database entry will automatically be marshalled to the correct form

src/elections/urls.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
import datetime
22

33
from fastapi import APIRouter, HTTPException, Request, status
44
from fastapi.responses import JSONResponse
@@ -53,9 +53,9 @@ def _default_election_positions(election_type: ElectionTypeEnum) -> list[Officer
5353
def _raise_if_bad_election_data(
5454
slug: str,
5555
election_type: str,
56-
datetime_start_nominations: datetime,
57-
datetime_start_voting: datetime,
58-
datetime_end_voting: datetime,
56+
datetime_start_nominations: datetime.datetime,
57+
datetime_start_voting: datetime.datetime,
58+
datetime_end_voting: datetime.datetime,
5959
available_positions: list[OfficerPositionEnum]
6060
):
6161
if election_type not in ElectionTypeEnum:
@@ -104,7 +104,7 @@ async def list_elections(
104104
detail="no election found"
105105
)
106106

107-
current_time = datetime.now()
107+
current_time = datetime.datetime.now(tz=datetime.UTC)
108108
if is_admin:
109109
election_metadata_list = [
110110
election.private_details(current_time)
@@ -136,7 +136,7 @@ async def get_election(
136136
db_session: database.DBSession,
137137
election_name: str
138138
):
139-
current_time = datetime.now()
139+
current_time = datetime.datetime.now(tz=datetime.UTC)
140140
slugified_name = slugify(election_name)
141141
election = await elections.crud.get_election(db_session, slugified_name)
142142
if election is None:
@@ -218,10 +218,10 @@ async def create_election(
218218
available_positions = body.available_positions
219219

220220
slugified_name = slugify(body.name)
221-
current_time = datetime.now()
222-
start_nominations = datetime.fromisoformat(body.datetime_start_nominations)
223-
start_voting = datetime.fromisoformat(body.datetime_start_voting)
224-
end_voting = datetime.fromisoformat(body.datetime_end_voting)
221+
current_time = datetime.datetime.now(tz=datetime.UTC)
222+
start_nominations = datetime.datetime.fromisoformat(body.datetime_start_nominations)
223+
start_voting = datetime.datetime.fromisoformat(body.datetime_start_voting)
224+
end_voting = datetime.datetime.fromisoformat(body.datetime_end_voting)
225225

226226
# TODO: We might be able to just use a validation function from Pydantic or SQLAlchemy to check this
227227
_raise_if_bad_election_data(
@@ -332,7 +332,7 @@ async def update_election(
332332
election = await elections.crud.get_election(db_session, slugified_name)
333333
if election is None:
334334
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="couldn't find updated election")
335-
return JSONResponse(election.private_details(datetime.now()))
335+
return JSONResponse(election.private_details(datetime.datetime.now(tz=datetime.UTC)))
336336

337337
@router.delete(
338338
"/{election_name:str}",

src/registrations/urls.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from datetime import datetime
1+
import datetime
22

33
from fastapi import APIRouter, HTTPException, Request, status
44
from fastapi.responses import JSONResponse
@@ -105,7 +105,7 @@ async def register_in_election(
105105
detail=f"{body.position} is not available to register for in this election"
106106
)
107107

108-
if election.status(datetime.now()) != ElectionStatusEnum.NOMINATIONS:
108+
if election.status(datetime.datetime.now(tz=datetime.UTC)) != ElectionStatusEnum.NOMINATIONS:
109109
raise HTTPException(
110110
status_code=status.HTTP_400_BAD_REQUEST,
111111
detail="registrations can only be made during the nomination period"
@@ -174,7 +174,7 @@ async def update_registration(
174174
)
175175

176176
# self updates can only be done during nomination period. Officer updates can be done whenever
177-
if election.status(datetime.now()) != ElectionStatusEnum.NOMINATIONS:
177+
if election.status(datetime.datetime.now(tz=datetime.UTC)) != ElectionStatusEnum.NOMINATIONS:
178178
raise HTTPException(
179179
status_code=status.HTTP_400_BAD_REQUEST,
180180
detail="speeches can only be updated during the nomination period"
@@ -237,7 +237,7 @@ async def delete_registration(
237237
detail=f"election with slug {slugified_name} does not exist"
238238
)
239239

240-
if election.status(datetime.now()) != ElectionStatusEnum.NOMINATIONS:
240+
if election.status(datetime.datetime.now(tz=datetime.UTC)) != ElectionStatusEnum.NOMINATIONS:
241241
raise HTTPException(
242242
status_code=status.HTTP_400_BAD_REQUEST,
243243
detail="registration can only be revoked during the nomination period"

tests/integration/test_elections.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
2-
from datetime import datetime, timedelta
2+
import datetime
3+
from datetime import timedelta
34

45
import pytest
56
from httpx import ASGITransport, AsyncClient
@@ -197,9 +198,9 @@ async def test_endpoints_admin(client, database_setup):
197198
response = await client.post("/election", json={
198199
"name": "testElection4",
199200
"type": "general_election",
200-
"datetime_start_nominations": (datetime.now() - timedelta(days=1)).isoformat(),
201-
"datetime_start_voting": (datetime.now() + timedelta(days=7)).isoformat(),
202-
"datetime_end_voting": (datetime.now() + timedelta(days=14)).isoformat(),
201+
"datetime_start_nominations": (datetime.datetime.now(tz=datetime.UTC) - timedelta(days=1)).isoformat(),
202+
"datetime_start_voting": (datetime.datetime.now(tz=datetime.UTC) + timedelta(days=7)).isoformat(),
203+
"datetime_end_voting": (datetime.datetime.now(tz=datetime.UTC) + timedelta(days=14)).isoformat(),
203204
"available_positions": ["president", "treasurer"],
204205
"survey_link": "https://youtu.be/dQw4w9WgXcQ?si=kZROi2tu-43MXPM5"
205206
})
@@ -208,9 +209,9 @@ async def test_endpoints_admin(client, database_setup):
208209
response = await client.post("/election", json={
209210
"name": "byElection4",
210211
"type": "by_election",
211-
"datetime_start_nominations": (datetime.now() - timedelta(days=1)).isoformat(),
212-
"datetime_start_voting": (datetime.now() + timedelta(days=7)).isoformat(),
213-
"datetime_end_voting": (datetime.now() + timedelta(days=14)).isoformat(),
212+
"datetime_start_nominations": (datetime.datetime.now(tz=datetime.UTC) - timedelta(days=1)).isoformat(),
213+
"datetime_start_voting": (datetime.datetime.now(tz=datetime.UTC) + timedelta(days=7)).isoformat(),
214+
"datetime_end_voting": (datetime.datetime.now(tz=datetime.UTC) + timedelta(days=14)).isoformat(),
214215
"survey_link": "https://youtu.be/dQw4w9WgXcQ?si=kZROi2tu-43MXPM5"
215216
})
216217
assert response.status_code == 200
@@ -275,9 +276,9 @@ async def test_endpoints_admin(client, database_setup):
275276
# update the above election
276277
response = await client.patch("/election/testElection4", json={
277278
"election_type": "general_election",
278-
"datetime_start_nominations": (datetime.now() - timedelta(days=1)).isoformat(),
279-
"datetime_start_voting": (datetime.now() + timedelta(days=7)).isoformat(),
280-
"datetime_end_voting": (datetime.now() + timedelta(days=14)).isoformat(),
279+
"datetime_start_nominations": (datetime.datetime.now(tz=datetime.UTC) - timedelta(days=1)).isoformat(),
280+
"datetime_start_voting": (datetime.datetime.now(tz=datetime.UTC) + timedelta(days=7)).isoformat(),
281+
"datetime_end_voting": (datetime.datetime.now(tz=datetime.UTC) + timedelta(days=14)).isoformat(),
281282
"available_positions": ["president", "vice-president", "treasurer"], # update this
282283
"survey_link": "https://youtu.be/dQw4w9WgXcQ?si=kZROi2tu-43MXPM5"
283284
})

0 commit comments

Comments
 (0)