From 9d4fd63cde03bb3aa89fbab57501a3a0e53830c3 Mon Sep 17 00:00:00 2001 From: SergeantPanda Date: Thu, 30 Oct 2025 16:13:45 -0500 Subject: [PATCH] Enhancement: Add date placeholders for custom dummy EPG based on output and source timezones. Closes #597 --- apps/output/views.py | 16 +++ frontend/src/components/forms/DummyEPG.jsx | 129 ++++++++++++++------- 2 files changed, 100 insertions(+), 45 deletions(-) diff --git a/apps/output/views.py b/apps/output/views.py index 8f1790b3..e4a5f0e5 100644 --- a/apps/output/views.py +++ b/apps/output/views.py @@ -634,6 +634,22 @@ def generate_custom_dummy_programs(channel_id, channel_name, now, num_days, cust minute = temp_date_output.minute logger.debug(f"Converted display time from {source_tz} to {output_tz}: {hour_24}:{minute:02d}") + # Add date placeholders based on the OUTPUT timezone + # This ensures {date}, {month}, {day}, {year} reflect the converted timezone + all_groups['date'] = temp_date_output.strftime('%Y-%m-%d') + all_groups['month'] = str(temp_date_output.month) + all_groups['day'] = str(temp_date_output.day) + all_groups['year'] = str(temp_date_output.year) + logger.debug(f"Converted date placeholders to {output_tz}: {all_groups['date']}") + else: + # No output timezone conversion - use source timezone for date + # Create temp date to get proper date in source timezone + temp_date_source = datetime.now(source_tz).replace(hour=hour_24, minute=minute, second=0, microsecond=0) + all_groups['date'] = temp_date_source.strftime('%Y-%m-%d') + all_groups['month'] = str(temp_date_source.month) + all_groups['day'] = str(temp_date_source.day) + all_groups['year'] = str(temp_date_source.year) + # Format 24-hour start time string - only include minutes if non-zero if minute > 0: all_groups['starttime24'] = f"{hour_24}:{minute:02d}" diff --git a/frontend/src/components/forms/DummyEPG.jsx b/frontend/src/components/forms/DummyEPG.jsx index 2b6984a8..d61c446f 100644 --- a/frontend/src/components/forms/DummyEPG.jsx +++ b/frontend/src/components/forms/DummyEPG.jsx @@ -226,10 +226,27 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { const sourceTimezone = form.values.custom_properties?.timezone || 'UTC'; const outputTimezone = form.values.custom_properties?.output_timezone; - if (outputTimezone && outputTimezone !== sourceTimezone) { - // Create a date in the source timezone - const sourceDate = dayjs() + // Determine the base date to use + let baseDate = dayjs().tz(sourceTimezone); + + // If date was extracted from pattern, use that instead of today + if (result.dateGroups.month && result.dateGroups.day) { + const extractedMonth = parseInt(result.dateGroups.month); + const extractedDay = parseInt(result.dateGroups.day); + const extractedYear = result.dateGroups.year + ? parseInt(result.dateGroups.year) + : dayjs().year(); // Default to current year if not provided + + baseDate = dayjs() .tz(sourceTimezone) + .year(extractedYear) + .month(extractedMonth - 1) // dayjs months are 0-indexed + .date(extractedDay); + } + + if (outputTimezone && outputTimezone !== sourceTimezone) { + // Create a date in the source timezone with extracted or current date + const sourceDate = baseDate .set('hour', hour24) .set('minute', minute) .set('second', 0); @@ -241,6 +258,13 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { hour24 = outputDate.hour(); const convertedMinute = outputDate.minute(); + // Add date placeholders based on the OUTPUT timezone + // This ensures {date}, {month}, {day}, {year} reflect the converted timezone + allGroups.date = outputDate.format('YYYY-MM-DD'); + allGroups.month = outputDate.month() + 1; // dayjs months are 0-indexed + allGroups.day = outputDate.date(); + allGroups.year = outputDate.year(); + // Format 24-hour start time string with converted time if (convertedMinute > 0) { allGroups.starttime24 = `${hour24.toString().padStart(2, '0')}:${convertedMinute.toString().padStart(2, '0')}`; @@ -269,6 +293,17 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { allGroups.starttime24_long = `${hour24.toString().padStart(2, '0')}:${convertedMinute.toString().padStart(2, '0')}`; } else { // No timezone conversion - use original logic + // Add date placeholders based on the source timezone + const sourceDate = baseDate + .set('hour', hour24) + .set('minute', minute) + .set('second', 0); + + allGroups.date = sourceDate.format('YYYY-MM-DD'); + allGroups.month = sourceDate.month() + 1; // dayjs months are 0-indexed + allGroups.day = sourceDate.date(); + allGroups.year = sourceDate.year(); + // Format 24-hour start time string if (minute > 0) { allGroups.starttime24 = `${hour24.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`; @@ -341,6 +376,10 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { endtime: allGroups.endtime, endtime24: allGroups.endtime24, endtime_long: allGroups.endtime_long, + date: allGroups.date, + month: allGroups.month, + day: allGroups.day, + year: allGroups.year, }; } catch (e) { // If parsing fails, leave starttime/endtime as placeholders @@ -781,7 +820,7 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { id="title_template" name="title_template" label="Title Template" - description="Format the EPG title using extracted groups. Use {starttime} (12-hour: '10 PM'), {starttime24} (24-hour: '22:00'), {endtime} (12-hour end), or {endtime24} (24-hour end). Example: {league} - {team1} vs {team2} ({starttime}-{endtime})" + description="Format the EPG title using extracted groups. Use {starttime} (12-hour: '10 PM'), {starttime24} (24-hour: '22:00'), {endtime} (12-hour end), {endtime24} (24-hour end), {date} (YYYY-MM-DD), {month}, {day}, or {year}. Date/time placeholders respect Output Timezone settings. Example: {league} - {team1} vs {team2} ({starttime}-{endtime})" placeholder="{league} - {team1} vs {team2}" value={titleTemplate} onChange={(e) => { @@ -795,7 +834,7 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { id="description_template" name="description_template" label="Description Template" - description="Format the EPG description using extracted groups. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), or {endtime24} (24-hour end). Example: Watch {team1} take on {team2} from {starttime} to {endtime}!" + description="Format the EPG description using extracted groups. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), {endtime24} (24-hour end), {date} (YYYY-MM-DD), {month}, {day}, or {year}. Date/time placeholders respect Output Timezone settings. Example: Watch {team1} take on {team2} on {date} from {starttime} to {endtime}!" placeholder="Watch {team1} take on {team2} in this exciting {league} matchup from {starttime} to {endtime}!" minRows={2} value={descriptionTemplate} @@ -825,7 +864,7 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { id="upcoming_title_template" name="upcoming_title_template" label="Upcoming Title Template" - description="Title for programs before the event starts. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), or {endtime24} (24-hour end). Example: {team1} vs {team2} starting at {starttime}." + description="Title for programs before the event starts. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), {endtime24} (24-hour end), {date} (YYYY-MM-DD), {month}, {day}, or {year}. Date/time placeholders respect Output Timezone settings. Example: {team1} vs {team2} starting at {starttime}." placeholder="{team1} vs {team2} starting at {starttime}." value={upcomingTitleTemplate} onChange={(e) => { @@ -842,7 +881,7 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { id="upcoming_description_template" name="upcoming_description_template" label="Upcoming Description Template" - description="Description for programs before the event. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), or {endtime24} (24-hour end). Example: Upcoming: Watch the {league} match up where the {team1} take on the {team2} from {starttime} to {endtime}!" + description="Description for programs before the event. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), {endtime24} (24-hour end), {date} (YYYY-MM-DD), {month}, {day}, or {year}. Date/time placeholders respect Output Timezone settings. Example: Upcoming: Watch the {league} match up where the {team1} take on the {team2} on {date} from {starttime} to {endtime}!" placeholder="Upcoming: Watch the {league} match up where the {team1} take on the {team2} from {starttime} to {endtime}!" minRows={2} value={upcomingDescriptionTemplate} @@ -860,7 +899,7 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { id="ended_title_template" name="ended_title_template" label="Ended Title Template" - description="Title for programs after the event has ended. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), or {endtime24} (24-hour end). Example: {team1} vs {team2} started at {starttime}." + description="Title for programs after the event has ended. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), {endtime24} (24-hour end), {date} (YYYY-MM-DD), {month}, {day}, or {year}. Date/time placeholders respect Output Timezone settings. Example: {team1} vs {team2} started at {starttime}." placeholder="{team1} vs {team2} started at {starttime}." value={endedTitleTemplate} onChange={(e) => { @@ -877,7 +916,7 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { id="ended_description_template" name="ended_description_template" label="Ended Description Template" - description="Description for programs after the event. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), or {endtime24} (24-hour end). Example: The {league} match between {team1} and {team2} ran from {starttime} to {endtime}." + description="Description for programs after the event. Use {starttime} (12-hour), {starttime24} (24-hour), {endtime} (12-hour end), {endtime24} (24-hour end), {date} (YYYY-MM-DD), {month}, {day}, or {year}. Date/time placeholders respect Output Timezone settings. Example: The {league} match between {team1} and {team2} on {date} ran from {starttime} to {endtime}." placeholder="The {league} match between {team1} and {team2} ran from {starttime} to {endtime}." minRows={2} value={endedDescriptionTemplate} @@ -1154,42 +1193,6 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { )} - {/* Show calculated time placeholders when time is extracted */} - {patternValidation.timeMatch && - Object.keys(patternValidation.calculatedPlaceholders || {}) - .length > 0 && ( - - - Available Time Placeholders: - - - {Object.entries( - patternValidation.calculatedPlaceholders - ).map(([key, value]) => ( - - - {'{' + key + '}'}: - - - {value} - - - ))} - - - )} - {patternValidation.dateMatch && ( @@ -1231,6 +1234,42 @@ const DummyEPGForm = ({ epg, isOpen, onClose }) => { )} + {/* Show calculated time placeholders when time is extracted */} + {patternValidation.timeMatch && + Object.keys(patternValidation.calculatedPlaceholders || {}) + .length > 0 && ( + + + Available Time Placeholders: + + + {Object.entries( + patternValidation.calculatedPlaceholders + ).map(([key, value]) => ( + + + {'{' + key + '}'}: + + + {value} + + + ))} + + + )} + {/* Output Preview */} {(patternValidation.titleMatch || patternValidation.timeMatch ||