game time formatting to remove space between time and AM/PM

This commit is contained in:
Chuck
2025-07-22 11:23:25 -05:00
parent a580d87876
commit c3ded3999f
10 changed files with 112 additions and 100 deletions

189
README.md
View File

@@ -62,84 +62,6 @@ The system supports live, recent, and upcoming game information for multiple spo
- Soccer - Soccer
- (Note, some of these sports seasons were not active during development and might need fine tuning when games are active) - (Note, some of these sports seasons were not active during development and might need fine tuning when games are active)
### Odds Ticker Feature
The system includes a comprehensive odds ticker that displays betting odds for upcoming sports games across multiple leagues. The ticker shows game times, team logos, spreads, money lines, and over/under totals in a scrolling format.
**Features:**
- **Multi-League Support**: NFL, NBA, MLB, NCAA Football
- **Configurable Leagues**: Choose which leagues to display
- **Favorite Teams Filter**: Option to show only favorite teams or all games
- **Team Logos**: Displays team logos alongside odds information
- **Comprehensive Odds**: Shows spreads, money lines, and over/under totals
- **Scrolling Display**: Smooth scrolling text with team logos
- **Time Display**: Shows game times in local timezone
**Display Format:**
```
[12:00 PM] DAL -6.5 ML -200 O/U 47.5 vs NYG ML +175
```
**Configuration:**
Add the following section to your `config/config.json`:
```json
{
"odds_ticker": {
"enabled": true,
"show_favorite_teams_only": false,
"enabled_leagues": ["nfl", "nba", "mlb", "ncaa_fb"],
"update_interval": 3600,
"scroll_speed": 2,
"scroll_delay": 0.05,
"display_duration": 30
}
}
```
**Testing:**
You can test the odds ticker functionality using:
```bash
python test_odds_ticker.py
```
### Persistent Caching Setup
The LEDMatrix system uses persistent caching to improve performance and reduce API calls. When running with `sudo`, the system needs a persistent cache directory that survives restarts.
**First-Time Setup:**
Run the setup script to create a persistent cache directory:
```bash
chmod +x setup_cache.sh
./setup_cache.sh
```
This will:
- Create `/var/cache/ledmatrix/` directory
- Set proper ownership to your user account
- Set permissions to allow the daemon user (which the system runs as) to write
- Test writability for both your user and the daemon user
**If You Still See Cache Warnings:**
If you see warnings about using temporary cache directory, run the permissions fix:
```bash
chmod +x fix_cache_permissions.sh
./fix_cache_permissions.sh
```
**Manual Setup:**
If you prefer to set up manually:
```bash
sudo mkdir -p /var/cache/ledmatrix
sudo chown $USER:$USER /var/cache/ledmatrix
sudo chmod 777 /var/cache/ledmatrix
```
**Cache Locations (in order of preference):**
1. `~/.ledmatrix_cache/` (user's home directory) - **Most persistent**
2. `/var/cache/ledmatrix/` (system cache directory) - **Persistent across restarts**
3. `/opt/ledmatrix/cache/` (alternative persistent location)
4. `/tmp/ledmatrix_cache/` (temporary directory) - **NOT persistent**
**Note:** If the system falls back to `/tmp/ledmatrix_cache/`, you'll see a warning message and the cache will not persist across restarts.
### Financial Information ### Financial Information
- Near real-time stock & crypto price updates - Near real-time stock & crypto price updates
@@ -387,7 +309,6 @@ The odds ticker displays betting odds for upcoming sports games. To configure it
{ {
"odds_ticker": { "odds_ticker": {
"enabled": true, "enabled": true,
"show_favorite_teams_only": false,
"enabled_leagues": ["nfl", "nba", "mlb", "ncaa_fb"], "enabled_leagues": ["nfl", "nba", "mlb", "ncaa_fb"],
"update_interval": 3600, "update_interval": 3600,
"scroll_speed": 2, "scroll_speed": 2,
@@ -400,23 +321,15 @@ The odds ticker displays betting odds for upcoming sports games. To configure it
### Configuration Options ### Configuration Options
- **`enabled`**: Enable/disable the odds ticker (default: false) - **`enabled`**: Enable/disable the odds ticker (default: false)
- **`show_favorite_teams_only`**: Show only games involving favorite teams (default: false)
- **`games_per_favorite_team`**: Number of upcoming games to show per favorite team, per league (default: 1)
- **`max_games_per_league`**: Maximum number of games to show per league (default: 5)
- **`show_odds_only`**: If true, only show games that have odds available (default: false)
- **`sort_order`**: How to sort games in the ticker. Options: `soonest` (default; by start time). (More options can be added in the future.)
- **`enabled_leagues`**: Array of leagues to display (options: "nfl", "nba", "mlb", "ncaa_fb") - **`enabled_leagues`**: Array of leagues to display (options: "nfl", "nba", "mlb", "ncaa_fb")
- **`update_interval`**: How often to fetch new odds data in seconds (default: 3600) - **`update_interval`**: How often to fetch new odds data in seconds (default: 3600)
- **`scroll_speed`**: Pixels to scroll per update (default: 1) - **`scroll_speed`**: Pixels to scroll per update (default: 1)
- **`scroll_delay`**: Delay between scroll updates in seconds (default: 0.05) - **`scroll_delay`**: Delay between scroll updates in seconds (default: 0.05)
- **`display_duration`**: How long to show each game in seconds (default: 30) - **`display_duration`**: How long to show each game in seconds (default: 30)
- **`loop`**: Whether to continuously loop the scroll animation (default: true). When false, the scroll stops when it reaches the end of the content.
**How it works:** **How it works:**
- If `show_favorite_teams_only` is true, the ticker will show the next `games_per_favorite_team` games for each favorite team in each enabled league, deduplicated and capped at `max_games_per_league` per league. - The ticker intelligently filters games based on the `"show_favorite_teams_only"` setting within each individual sport's configuration block (e.g., `"nfl_scoreboard"`). If set to `true` for a sport, only favorite teams from that sport will appear in the ticker.
- If `show_favorite_teams_only` is false, the ticker will show all games for all teams (up to `max_games_per_league` per league). - Games are sorted by the soonest start time.
- If `show_odds_only` is true, only games with odds will be shown.
- Games are sorted by `sort_order` (default: soonest).
### Display Format ### Display Format
@@ -668,7 +581,105 @@ To get these credentials:
* Ensure your firewall (Windows Firewall) allows YTM Desktop app to access local networks. * Ensure your firewall (Windows Firewall) allows YTM Desktop app to access local networks.
----------------------------------------------------------------------------------- -----------------------------------------------------------------------------------
### Favorite Team Filtering
Across all sports displays (NFL, MLB, NBA, etc.), you can control which games are shown using the `"show_favorite_teams_only"` and `"favorite_teams"` settings in your `config/config.json`.
**How it Works:**
* **`"show_favorite_teams_only": true`**: When this is set to `true` within a specific sport's configuration (e.g., in the `"nfl_scoreboard"` block), the system will **only** fetch and display games (Live, Recent, and Upcoming) that involve one of the teams listed in your `"favorite_teams"` array for that sport. This is the best way to reduce API calls and keep the display focused.
* **`"show_favorite_teams_only": false`**: When set to `false` (or omitted), the system will display **all** available games for that sport, ignoring the `"favorite_teams"` list completely.
**Example `config.json` for NFL:**
```json
"nfl_scoreboard": {
"enabled": true,
"show_odds": true,
"show_favorite_teams_only": true, // <-- Only shows games for DAL & TB
"favorite_teams": ["DAL", "TB"],
"fetch_past_games": 1,
"fetch_future_games": 1
},
```
### Odds Ticker Feature
The system includes a comprehensive odds ticker that displays betting odds for upcoming sports games. The ticker respects the `show_favorite_teams_only` setting from each individual sports module. For example, if `"show_favorite_teams_only": true` is set in the `nfl_scoreboard` config, the odds ticker will only show odds for your favorite NFL teams.
**Features:**
- **Multi-League Support**: NFL, NBA, MLB, NCAA Football
- **Configurable Leagues**: Choose which leagues to display
- **Favorite Teams Filter**: Obeys the `show_favorite_teams_only` setting in each sport's configuration block.
- **Team Logos**: Displays team logos alongside odds information
- **Comprehensive Odds**: Shows spreads, money lines, and over/under totals
- **Scrolling Display**: Smooth scrolling text with team logos
- **Time Display**: Shows game times in local timezone
**Display Format:**
```
[12:00 PM] DAL -6.5 ML -200 O/U 47.5 vs NYG ML +175
```
**Configuration:**
Add the following section to your `config/config.json`:
```json
{
"odds_ticker": {
"enabled": true,
"enabled_leagues": ["nfl", "nba", "mlb", "ncaa_fb"],
"update_interval": 3600,
"scroll_speed": 2,
"scroll_delay": 0.05,
"display_duration": 30
}
}
```
**Testing:**
You can test the odds ticker functionality using:
```bash
python test_odds_ticker.py
```
### Persistent Caching Setup
The LEDMatrix system uses persistent caching to improve performance and reduce API calls. When running with `sudo`, the system needs a persistent cache directory that survives restarts.
**First-Time Setup:**
Run the setup script to create a persistent cache directory:
```bash
chmod +x setup_cache.sh
./setup_cache.sh
```
This will:
- Create `/var/cache/ledmatrix/` directory
- Set proper ownership to your user account
- Set permissions to allow the daemon user (which the system runs as) to write
- Test writability for both your user and the daemon user
**If You Still See Cache Warnings:**
If you see warnings about using temporary cache directory, run the permissions fix:
```bash
chmod +x fix_cache_permissions.sh
./fix_cache_permissions.sh
```
**Manual Setup:**
If you prefer to set up manually:
```bash
sudo mkdir -p /var/cache/ledmatrix
sudo chown $USER:$USER /var/cache/ledmatrix
sudo chmod 777 /var/cache/ledmatrix
```
**Cache Locations (in order of preference):**
1. `~/.ledmatrix_cache/` (user's home directory) - **Most persistent**
2. `/var/cache/ledmatrix/` (system cache directory) - **Persistent across restarts**
3. `/opt/ledmatrix/cache/` (alternative persistent location)
4. `/tmp/ledmatrix_cache/` (temporary directory) - **NOT persistent**
**Note:** If the system falls back to `/tmp/ledmatrix_cache/`, you'll see a warning message and the cache will not persist across restarts.
------------------------------------------------------------------------------------
## Before Running the Display ## Before Running the Display
- To allow the script to properly access fonts, you need to set the correct permissions on your home directory: - To allow the script to properly access fonts, you need to set the correct permissions on your home directory:
```bash ```bash

View File

@@ -298,7 +298,7 @@ class BaseMiLBManager:
dt = dt.replace(tzinfo=pytz.UTC) dt = dt.replace(tzinfo=pytz.UTC)
local_dt = dt.astimezone(tz) local_dt = dt.astimezone(tz)
return local_dt.strftime("%I:%M %p").lstrip('0') return local_dt.strftime("%I:%M%p").lstrip('0')
except Exception as e: except Exception as e:
logger.error(f"Error formatting game time: {e}") logger.error(f"Error formatting game time: {e}")
return "TBD" return "TBD"

View File

@@ -372,7 +372,7 @@ class BaseMLBManager:
dt = dt.replace(tzinfo=pytz.UTC) dt = dt.replace(tzinfo=pytz.UTC)
local_dt = dt.astimezone(tz) local_dt = dt.astimezone(tz)
return local_dt.strftime("%I:%M %p").lstrip('0') return local_dt.strftime("%I:%M%p").lstrip('0')
except Exception as e: except Exception as e:
logger.error(f"Error formatting game time: {e}") logger.error(f"Error formatting game time: {e}")
return "TBD" return "TBD"

View File

@@ -442,7 +442,7 @@ class BaseNBAManager:
if start_time_utc: if start_time_utc:
# Convert to local time # Convert to local time
local_time = start_time_utc.astimezone(self._get_timezone()) local_time = start_time_utc.astimezone(self._get_timezone())
game_time = local_time.strftime("%I:%M %p").lstrip('0') game_time = local_time.strftime("%I:%M%p").lstrip('0')
game_date = local_time.strftime("%-m/%-d") game_date = local_time.strftime("%-m/%-d")
# Calculate if game is within recent window # Calculate if game is within recent window

View File

@@ -361,7 +361,7 @@ class BaseNCAABaseballManager:
if dt.tzinfo is None: if dt.tzinfo is None:
dt = dt.replace(tzinfo=pytz.UTC) dt = dt.replace(tzinfo=pytz.UTC)
local_dt = dt.astimezone(tz) local_dt = dt.astimezone(tz)
return local_dt.strftime("%I:%M %p").lstrip('0') return local_dt.strftime("%I:%M%p").lstrip('0')
except Exception as e: except Exception as e:
logger.error(f"[NCAABaseball] Error formatting game time: {e}") logger.error(f"[NCAABaseball] Error formatting game time: {e}")
return "TBD" return "TBD"

View File

@@ -483,7 +483,7 @@ class BaseNCAAFBManager: # Renamed class
game_time, game_date = "", "" game_time, game_date = "", ""
if start_time_utc: if start_time_utc:
local_time = start_time_utc.astimezone(self._get_timezone()) local_time = start_time_utc.astimezone(self._get_timezone())
game_time = local_time.strftime("%I:%M %p").lstrip('0') game_time = local_time.strftime("%I:%M%p").lstrip('0')
game_date = local_time.strftime("%-m/%-d") game_date = local_time.strftime("%-m/%-d")
# --- Football Specific Details (Likely same for NFL/NCAAFB) --- # --- Football Specific Details (Likely same for NFL/NCAAFB) ---

View File

@@ -449,7 +449,7 @@ class BaseNCAAMBasketballManager:
if start_time_utc: if start_time_utc:
# Convert to local time # Convert to local time
local_time = start_time_utc.astimezone(self._get_timezone()) local_time = start_time_utc.astimezone(self._get_timezone())
game_time = local_time.strftime("%I:%M %p").lstrip('0') game_time = local_time.strftime("%I:%M%p").lstrip('0')
game_date = local_time.strftime("%-m/%-d") game_date = local_time.strftime("%-m/%-d")
# Calculate if game is within recent window # Calculate if game is within recent window

View File

@@ -408,10 +408,11 @@ class BaseNFLManager: # Renamed class
# Remove early filtering - let individual managers handle their own filtering # Remove early filtering - let individual managers handle their own filtering
# This allows shared data to contain all games, and each manager can filter as needed # This allows shared data to contain all games, and each manager can filter as needed
game_time, game_date = "", "" game_time = ""
game_date = ""
if start_time_utc: if start_time_utc:
local_time = start_time_utc.astimezone(self._get_timezone()) local_time = start_time_utc.astimezone(self._get_timezone())
game_time = local_time.strftime("%I:%M %p").lstrip('0') game_time = local_time.strftime("%I:%M%p").lstrip('0')
game_date = local_time.strftime("%-m/%-d") game_date = local_time.strftime("%-m/%-d")
# --- NFL Specific Details --- # --- NFL Specific Details ---

View File

@@ -392,7 +392,7 @@ class BaseNHLManager:
if start_time_utc: if start_time_utc:
# Convert to local time # Convert to local time
local_time = start_time_utc.astimezone(self._get_timezone()) local_time = start_time_utc.astimezone(self._get_timezone())
game_time = local_time.strftime("%-I:%M %p") game_time = local_time.strftime("%-I:%M%p")
game_date = local_time.strftime("%-m/%-d") game_date = local_time.strftime("%-m/%-d")
# Calculate if game is within recent window # Calculate if game is within recent window

View File

@@ -321,7 +321,7 @@ class OddsTickerManager:
if game_time.tzinfo is None: if game_time.tzinfo is None:
game_time = game_time.replace(tzinfo=pytz.UTC) game_time = game_time.replace(tzinfo=pytz.UTC)
local_time = game_time.astimezone(tz) local_time = game_time.astimezone(tz)
time_str = local_time.strftime("%I:%M %p").lstrip('0') time_str = local_time.strftime("%I:%M%p").lstrip('0')
return f"[{time_str}] {game['away_team']} vs {game['home_team']} (No odds)" return f"[{time_str}] {game['away_team']} vs {game['home_team']} (No odds)"
@@ -426,7 +426,7 @@ class OddsTickerManager:
# Capitalize full day name, e.g., 'Tuesday' # Capitalize full day name, e.g., 'Tuesday'
day_text = local_time.strftime("%A") day_text = local_time.strftime("%A")
date_text = local_time.strftime("%-m/%d") date_text = local_time.strftime("%-m/%d")
time_text = local_time.strftime("%I:%M %p").lstrip('0') time_text = local_time.strftime("%I:%M%p").lstrip('0')
# Team and record text # Team and record text
away_team_text = f"{game.get('away_team', 'N/A')} ({game.get('away_record', '') or 'N/A'})" away_team_text = f"{game.get('away_team', 'N/A')} ({game.get('away_record', '') or 'N/A'})"