Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 75 additions & 16 deletions src/scripts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface TimeZone {
abbreviation: string; // Timezone abbreviation (e.g., "EDT", "JST")
daylight?: DaylightData;
isCustom?: boolean; // Flag to indicate custom timezone
isOffCycle?: boolean; // Flag to indicate timezone was manually selected in alternate DST/Standard state (e.g., PST when PDT is current, prevents automatic DST transitions on date changes)
coordinates?: { latitude: number; longitude: number }; // Optional coordinates for custom timezones
}

Expand Down Expand Up @@ -924,7 +925,10 @@ export class TimelineManager {
}

// Initialize modal with callback and selected date
this.modal = new TimezoneModal((timezone: TimeZone) => this.addTimezone(timezone), this.selectedDate);
this.modal = new TimezoneModal(
(timezone: TimeZone, isOffCycle?: boolean) => this.addTimezone(timezone, isOffCycle),
this.selectedDate,
);

// Initialize datetime modal with callback
this.dateTimeModal = new DateTimeModal((dateTime: Date) => this.setSelectedDate(dateTime));
Expand Down Expand Up @@ -958,11 +962,13 @@ export class TimelineManager {
this.renderTimeline();
}

public addTimezone(timezone: TimeZone): void {
public addTimezone(timezone: TimeZone, isOffCycle?: boolean): void {
// Check if timezone already exists
const exists = this.selectedTimezones.find(tz => tz.iana === timezone.iana);
if (!exists) {
this.selectedTimezones.push(timezone);
// Create a copy of the timezone and set the off-cycle flag if specified
const timezoneToAdd = isOffCycle !== undefined ? { ...timezone, isOffCycle } : timezone;
this.selectedTimezones.push(timezoneToAdd);
this.renderTimeline();
}
}
Expand All @@ -983,22 +989,44 @@ export class TimelineManager {
public setSelectedDate(date: Date): void {
this.selectedDate = new Date(date);

// Recalculate timezones for the new date to handle DST transitions
const { numRows } = getTimelineDimensions();
this.selectedTimezones = getTimezonesForTimeline(numRows, this.selectedDate);
// Handle DST transitions while preserving off-cycle timezones
const updatedTimezones: TimeZone[] = [];
const allTimezones = getAllTimezonesOrdered(this.selectedDate);

for (const timezone of this.selectedTimezones) {
if (timezone.isOffCycle || timezone.isCustom) {
// Preserve off-cycle and custom timezones without DST adjustment
updatedTimezones.push(timezone);
} else {
// For regular timezones, recalculate for new date to handle DST transitions
const updatedTimezone = allTimezones.find(tz => tz.iana === timezone.iana);
if (updatedTimezone) {
updatedTimezones.push(updatedTimezone);
} else {
// Fallback: keep original if not found in updated list
updatedTimezones.push(timezone);
}
}
}

this.selectedTimezones = updatedTimezones;

// Re-add any custom timezones
// Re-add any saved custom timezones that were not preserved in the updated selection
const customTimezones = CustomTimezoneManager.getCustomTimezones();
const existingIanas = new Set(this.selectedTimezones.map(tz => tz.iana));

for (const customTz of customTimezones) {
// Only add if not already included
const exists = this.selectedTimezones.find(tz => tz.iana === customTz.iana);
if (!exists) {
if (!existingIanas.has(customTz.iana)) {
this.selectedTimezones.push(customTz);
}
}

// Update the modal with the new date and recalculated timezones
this.modal = new TimezoneModal((timezone: TimeZone) => this.addTimezone(timezone), this.selectedDate);
this.modal = new TimezoneModal(
(timezone: TimeZone, isOffCycle?: boolean) => this.addTimezone(timezone, isOffCycle),
this.selectedDate,
);

this.renderTimeline();
}
Expand Down Expand Up @@ -1781,15 +1809,16 @@ export class TimezoneModal {
private downButton: HTMLElement;
private timezones: TimeZone[];
private groupedTimezones: GroupedTimezone[];
private groupedTimezonesLookup: Map<string, GroupedTimezone>; // Lookup map for efficient timezone searches
private filteredTimezones: TimeZone[];
private filteredGroups: GroupedTimezone[];
private selectedIndex = 0;
private currentUserTimezone: string;
private onTimezoneSelectedCallback: ((timezone: TimeZone) => void) | undefined;
private onTimezoneSelectedCallback: ((timezone: TimeZone, isOffCycle?: boolean) => void) | undefined;
private userSearchQuery = ''; // Store user's search query separately
private selectedDate: Date; // Date to use for timezone calculations

constructor(onTimezoneSelected?: (timezone: TimeZone) => void, selectedDate?: Date) {
constructor(onTimezoneSelected?: (timezone: TimeZone, isOffCycle?: boolean) => void, selectedDate?: Date) {
this.selectedDate = selectedDate || new Date();
this.modal = document.getElementById('timezone-modal') as HTMLElement;
this.overlay = document.getElementById('timezone-modal-overlay') as HTMLElement;
Expand All @@ -1807,6 +1836,15 @@ export class TimezoneModal {
// Get both grouped and flat timezone lists
this.groupedTimezones = getGroupedTimezones(this.selectedDate);

// Create lookup map for efficient timezone group searches by IANA identifier
this.groupedTimezonesLookup = new Map();
for (const group of this.groupedTimezones) {
this.groupedTimezonesLookup.set(group.current.iana, group);
if (group.alternate) {
this.groupedTimezonesLookup.set(group.alternate.iana, group);
}
}

// Combine standard and custom timezones using the selected date
const standardTimezones = getAllTimezonesOrdered(this.selectedDate);
const customTimezones = CustomTimezoneManager.getCustomTimezones();
Expand Down Expand Up @@ -1885,7 +1923,18 @@ export class TimezoneModal {
// Check if query is an offset pattern (GMT-7, UTC+5:30, PDT-7, etc.)
const offsetMatch = this.parseOffsetQuery(query);

for (const timezone of this.timezones) {
// Also search in grouped timezones for alternate variants
const allSearchTargets: TimeZone[] = [...this.timezones];

// Add alternate timezones from grouped timezones to search targets
const existingIanas = new Set(allSearchTargets.map(tz => tz.iana));
for (const group of this.groupedTimezones) {
if (group.alternate && !existingIanas.has(group.alternate.iana)) {
allSearchTargets.push(group.alternate);
}
}

for (const timezone of allSearchTargets) {
let score = 0;

// Handle offset search
Expand Down Expand Up @@ -2187,9 +2236,9 @@ export class TimezoneModal {
if (plusBtn && group.alternate) {
plusBtn.addEventListener('click', e => {
e.stopPropagation();
// Select the alternate timezone directly
// Select the alternate timezone directly and mark as off-cycle
if (this.onTimezoneSelectedCallback && group.alternate) {
this.onTimezoneSelectedCallback(group.alternate);
this.onTimezoneSelectedCallback(group.alternate, true); // Mark alternate as off-cycle
}
this.close();
});
Expand Down Expand Up @@ -2288,8 +2337,18 @@ export class TimezoneModal {
const selectedTimezone = this.filteredTimezones[this.selectedIndex];
if (selectedTimezone) {
console.log('Selected timezone:', selectedTimezone);

// Check if this is an off-cycle variant by comparing with grouped timezones
let isOffCycle = false;
const matchingGroup = this.groupedTimezonesLookup.get(selectedTimezone.iana);

if (matchingGroup && matchingGroup.alternate && matchingGroup.alternate.iana === selectedTimezone.iana) {
// This is the alternate timezone (off-cycle variant)
isOffCycle = true;
}

if (this.onTimezoneSelectedCallback) {
this.onTimezoneSelectedCallback(selectedTimezone);
this.onTimezoneSelectedCallback(selectedTimezone, isOffCycle);
}
this.close();
}
Expand Down