-
Notifications
You must be signed in to change notification settings - Fork 8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Booking View with daylight savings #306
Changes from 5 commits
69cfaec
db73ba0
948922f
07bc7e6
cce160d
962c96b
9b12810
0181566
40fb2e1
753c22a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,9 +2,12 @@ | |
|
||
Definitions of database tables and their relationships. | ||
""" | ||
import datetime | ||
import enum | ||
import os | ||
import uuid | ||
import zoneinfo | ||
|
||
from sqlalchemy import Column, ForeignKey, Integer, String, DateTime, Enum, Boolean, JSON, Date, Time | ||
from sqlalchemy_utils import StringEncryptedType, ChoiceType | ||
from sqlalchemy_utils.types.encrypted.encrypted_type import AesEngine | ||
|
@@ -128,9 +131,9 @@ class Calendar(Base): | |
connected = Column(Boolean, index=True, default=False) | ||
connected_at = Column(DateTime) | ||
|
||
owner = relationship("Subscriber", back_populates="calendars") | ||
appointments = relationship("Appointment", cascade="all,delete", back_populates="calendar") | ||
schedules = relationship("Schedule", cascade="all,delete", back_populates="calendar") | ||
owner: Subscriber = relationship("Subscriber", back_populates="calendars") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I got annoyed at the lack of type hinting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh nice, thanks for the types. And sorry for not doing it right from the start 😬 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no worries! 😄 |
||
appointments: list["Appointment"] = relationship("Appointment", cascade="all,delete", back_populates="calendar") | ||
schedules: list["Schedule"] = relationship("Schedule", cascade="all,delete", back_populates="calendar") | ||
|
||
|
||
class Appointment(Base): | ||
|
@@ -200,28 +203,40 @@ class Slot(Base): | |
class Schedule(Base): | ||
__tablename__ = "schedules" | ||
|
||
id = Column(Integer, primary_key=True, index=True) | ||
calendar_id = Column(Integer, ForeignKey("calendars.id")) | ||
active = Column(Boolean, index=True, default=True) | ||
name = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=255), index=True) | ||
location_type = Column(Enum(LocationType), default=LocationType.inperson) | ||
location_url = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=2048)) | ||
details = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=255)) | ||
start_date = Column(StringEncryptedType(Date, secret, AesEngine, "pkcs5", length=255), index=True) | ||
end_date = Column(StringEncryptedType(Date, secret, AesEngine, "pkcs5", length=255), index=True) | ||
start_time = Column(StringEncryptedType(Time, secret, AesEngine, "pkcs5", length=255), index=True) | ||
end_time = Column(StringEncryptedType(Time, secret, AesEngine, "pkcs5", length=255), index=True) | ||
earliest_booking = Column(Integer, default=1440) # in minutes, defaults to 24 hours | ||
farthest_booking = Column(Integer, default=20160) # in minutes, defaults to 2 weeks | ||
weekdays = Column(JSON, default="[1,2,3,4,5]") # list of ISO weekdays, Mo-Su => 1-7 | ||
slot_duration = Column(Integer, default=30) # defaults to 30 minutes | ||
id: int = Column(Integer, primary_key=True, index=True) | ||
calendar_id: int = Column(Integer, ForeignKey("calendars.id")) | ||
active: bool = Column(Boolean, index=True, default=True) | ||
name: str = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=255), index=True) | ||
location_type: LocationType = Column(Enum(LocationType), default=LocationType.inperson) | ||
location_url: str = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=2048)) | ||
details: str = Column(StringEncryptedType(String, secret, AesEngine, "pkcs5", length=255)) | ||
start_date: datetime.date = Column(StringEncryptedType(Date, secret, AesEngine, "pkcs5", length=255), index=True) | ||
end_date: datetime.date = Column(StringEncryptedType(Date, secret, AesEngine, "pkcs5", length=255), index=True) | ||
start_time: datetime.time = Column(StringEncryptedType(Time, secret, AesEngine, "pkcs5", length=255), index=True) | ||
end_time: datetime.time = Column(StringEncryptedType(Time, secret, AesEngine, "pkcs5", length=255), index=True) | ||
earliest_booking: int = Column(Integer, default=1440) # in minutes, defaults to 24 hours | ||
farthest_booking: int = Column(Integer, default=20160) # in minutes, defaults to 2 weeks | ||
weekdays: str | dict = Column(JSON, default="[1,2,3,4,5]") # list of ISO weekdays, Mo-Su => 1-7 | ||
slot_duration: int = Column(Integer, default=30) # defaults to 30 minutes | ||
|
||
# What (if any) meeting link will we generate once the meeting is booked | ||
meeting_link_provider = Column(StringEncryptedType(ChoiceType(MeetingLinkProviderType), secret, AesEngine, "pkcs5", length=255), default=MeetingLinkProviderType.none, index=False) | ||
|
||
calendar = relationship("Calendar", back_populates="schedules") | ||
availabilities = relationship("Availability", cascade="all,delete", back_populates="schedule") | ||
slots = relationship("Slot", cascade="all,delete", back_populates="schedule") | ||
meeting_link_provider: MeetingLinkProviderType = Column(StringEncryptedType(ChoiceType(MeetingLinkProviderType), secret, AesEngine, "pkcs5", length=255), default=MeetingLinkProviderType.none, index=False) | ||
|
||
calendar: Calendar = relationship("Calendar", back_populates="schedules") | ||
availabilities: "Availability" = relationship("Availability", cascade="all,delete", back_populates="schedule") | ||
MelissaAutumn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
slots: list[Slot] = relationship("Slot", cascade="all,delete", back_populates="schedule") | ||
|
||
@property | ||
def start_time_local(self) -> datetime.time: | ||
"""Start Time in the Schedule's Calendar's Owner's timezone""" | ||
time_of_save = self.time_updated.replace(hour=self.start_time.hour, minute=self.start_time.minute, second=0) | ||
return time_of_save.astimezone(zoneinfo.ZoneInfo(self.calendar.owner.timezone)).time() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we save start_time we convert it and store it as UTC. To get back their original intended start time we simply grab the date the user saved their schedule, append the UTC start time and convert that back to their timezone. |
||
|
||
@property | ||
def end_time_local(self) -> datetime.time: | ||
"""End Time in the Schedule's Calendar's Owner's timezone""" | ||
time_of_save = self.time_updated.replace(hour=self.end_time.hour, minute=self.end_time.minute, second=0) | ||
return time_of_save.astimezone(zoneinfo.ZoneInfo(self.calendar.owner.timezone)).time() | ||
|
||
|
||
class Availability(Base): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -231,7 +231,7 @@ def list_events(self, start, end): | |
monkeypatch.setattr(CalDavConnector, "__init__", MockCaldavConnector.__init__) | ||
monkeypatch.setattr(CalDavConnector, "list_events", MockCaldavConnector.list_events) | ||
|
||
start_date = date(2024, 4, 1) | ||
start_date = date(2024, 3, 1) | ||
start_time = time(9) | ||
end_time = time(17) | ||
|
||
|
@@ -262,9 +262,11 @@ def list_events(self, start, end): | |
slots = data['slots'] | ||
|
||
# Based off the earliest_booking our earliest slot is tomorrow at 9:00am | ||
assert slots[0]['start'] == '2024-04-02T09:00:00' | ||
# Note: this should be in PST (Pacific Standard Time) | ||
assert slots[0]['start'] == '2024-03-04T09:00:00-08:00' | ||
# Based off the farthest_booking our latest slot is 4:30pm | ||
assert slots[-1]['start'] == '2024-04-15T16:30:00' | ||
# Note: This should be in PDT (Pacific Daylight Time) | ||
assert slots[-1]['start'] == '2024-03-15T16:30:00-07:00' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let me know if we need a more exhaustive test here. make_pro_subscriber creates a subscriber (using faker data) and is hardcoded to America/Vancouver for the timezone. Since daylight savings did happen this year this shouldn't break if we remove daylight savings from BC going forward. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fine with me. Since we (you) just had a perfect example for DST, makes totally sense to just take that as testing example 👍🏻 |
||
|
||
# Check availability over a year from now | ||
with freeze_time(date(2025, 6, 1)): | ||
|
@@ -277,8 +279,8 @@ def list_events(self, start, end): | |
data = response.json() | ||
slots = data['slots'] | ||
|
||
assert slots[0]['start'] == '2025-06-02T09:00:00' | ||
assert slots[-1]['start'] == '2025-06-13T16:30:00' | ||
assert slots[0]['start'] == '2025-06-02T09:00:00-07:00' | ||
assert slots[-1]['start'] == '2025-06-13T16:30:00-07:00' | ||
|
||
# Check availability with a start date day greater than the farthest_booking day | ||
with freeze_time(date(2025, 6, 27)): | ||
|
@@ -291,5 +293,5 @@ def list_events(self, start, end): | |
data = response.json() | ||
slots = data['slots'] | ||
|
||
assert slots[0]['start'] == '2025-06-30T09:00:00' | ||
assert slots[-1]['start'] == '2025-07-11T16:30:00' | ||
assert slots[0]['start'] == '2025-06-30T09:00:00-07:00' | ||
assert slots[-1]['start'] == '2025-07-11T16:30:00-07:00' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had ruff set to run on save. Hence the unused import removals, and re-appearence of the zoneinfo.