fix(polymarket-browse): add --timezone CLI argument for display timezone
- Add parse_timezone() function supporting UTC+X format - Add --timezone argument (default: UTC+7) - Module-level _DISPLAY_TZ controls all time display formatting - get_header_date() and _get_time_data() use _DISPLAY_TZ - Add TestTimezoneParsing unit tests - Update SKILL.md documentation
This commit is contained in:
@@ -35,7 +35,7 @@ hermes mcp add polymarket https://docs.polymarket.com/mcp
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
polymarket-browse [--category "Counter Strike"] [--limit 5] [--matches N] [--non-matches N] [--search "TeamName"] [--matches-only] [--non-matches-only] [--detail N] [--raw] [--telegram] [--no-cache] [--max-total N]
|
polymarket-browse [--category "Counter Strike"] [--limit 5] [--matches N] [--non-matches N] [--search "TeamName"] [--matches-only] [--non-matches-only] [--detail N] [--raw] [--telegram] [--no-cache] [--max-total N] [--timezone UTC+X]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Arguments
|
## Arguments
|
||||||
@@ -52,6 +52,7 @@ polymarket-browse [--category "Counter Strike"] [--limit 5] [--matches N] [--non
|
|||||||
- `--raw` : Show all events without tradeable filter (for debugging). Includes fetch stats.
|
- `--raw` : Show all events without tradeable filter (for debugging). Includes fetch stats.
|
||||||
- `--no-cache` : Disable caching and fetch fresh data from the API.
|
- `--no-cache` : Disable caching and fetch fresh data from the API.
|
||||||
- `--max-total` : Maximum total events to fetch before early exit. Default: no limit. Useful for quick snapshots.
|
- `--max-total` : Maximum total events to fetch before early exit. Default: no limit. Useful for quick snapshots.
|
||||||
|
- `--timezone` : Timezone for displaying times. Format: `UTC+X` or `UTC-X` (e.g., `UTC+7`, `UTC-5`). Default: UTC+7 (WIB).
|
||||||
- `--telegram` : Send results to Telegram. Requires `BOT_TOKEN` and `CHAT_ID` in environment variables.
|
- `--telegram` : Send results to Telegram. Requires `BOT_TOKEN` and `CHAT_ID` in environment variables.
|
||||||
|
|
||||||
## Output Format
|
## Output Format
|
||||||
|
|||||||
@@ -98,6 +98,45 @@ PAGE_SIZE = 50
|
|||||||
MAX_RETRIES = 5
|
MAX_RETRIES = 5
|
||||||
INITIAL_RETRY_DELAY = 2 # exponential backoff starts at 2s
|
INITIAL_RETRY_DELAY = 2 # exponential backoff starts at 2s
|
||||||
WIB = timezone(timedelta(hours=7)) # UTC+7 for Indonesian users
|
WIB = timezone(timedelta(hours=7)) # UTC+7 for Indonesian users
|
||||||
|
_DISPLAY_TZ = WIB # Module-level timezone for display (configurable via --timezone)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_timezone(tz_str: str) -> timezone:
|
||||||
|
"""
|
||||||
|
Parse timezone string to datetime.timezone.
|
||||||
|
Supports: UTC offset format (UTC+7, UTC-5).
|
||||||
|
Falls back to WIB (UTC+7) on parse failure.
|
||||||
|
"""
|
||||||
|
tz_str = tz_str.strip()
|
||||||
|
if tz_str.startswith("UTC"):
|
||||||
|
offset_str = tz_str[3:].strip()
|
||||||
|
if not offset_str:
|
||||||
|
return timezone.utc
|
||||||
|
sign = -1 if offset_str[0] == "-" else 1
|
||||||
|
if offset_str[0] in "+-":
|
||||||
|
offset_str = offset_str[1:]
|
||||||
|
try:
|
||||||
|
if ":" in offset_str:
|
||||||
|
hours, minutes = offset_str.split(":")
|
||||||
|
hours = int(hours)
|
||||||
|
minutes = int(minutes)
|
||||||
|
else:
|
||||||
|
hours = int(offset_str)
|
||||||
|
minutes = 0
|
||||||
|
total_minutes = hours * 60 + minutes
|
||||||
|
if sign == -1:
|
||||||
|
total_minutes = -total_minutes
|
||||||
|
return timezone(timedelta(minutes=total_minutes))
|
||||||
|
except ValueError:
|
||||||
|
return WIB
|
||||||
|
return WIB
|
||||||
|
try:
|
||||||
|
from datetime import ZoneInfo
|
||||||
|
|
||||||
|
return ZoneInfo(tz_str).utcoffset(None)
|
||||||
|
except Exception:
|
||||||
|
return WIB
|
||||||
|
|
||||||
|
|
||||||
GAME_CATEGORIES = {
|
GAME_CATEGORIES = {
|
||||||
"All Esports": "Esports",
|
"All Esports": "Esports",
|
||||||
@@ -453,12 +492,12 @@ def _get_time_data(e: dict[str, Any], tz: timezone | None = None) -> TimeData:
|
|||||||
Args:
|
Args:
|
||||||
e: Event dict with 'startTime' or 'startDate' key.
|
e: Event dict with 'startTime' or 'startDate' key.
|
||||||
tz: datetime.timezone for abs_time formatting.
|
tz: datetime.timezone for abs_time formatting.
|
||||||
Defaults to WIB (UTC+7).
|
Defaults to _DISPLAY_TZ (set via --timezone, or WIB).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
TimeData with time_status, time_urgency, and abs_time
|
TimeData with time_status, time_urgency, and abs_time
|
||||||
"""
|
"""
|
||||||
tz = tz or WIB
|
tz = tz or _DISPLAY_TZ
|
||||||
start_str = e.get("startTime") or e.get("startDate", "")
|
start_str = e.get("startTime") or e.get("startDate", "")
|
||||||
|
|
||||||
if not start_str:
|
if not start_str:
|
||||||
@@ -819,11 +858,10 @@ def format_detail_event(e: dict[str, Any]) -> DetailEvent:
|
|||||||
|
|
||||||
|
|
||||||
def get_header_date() -> str:
|
def get_header_date() -> str:
|
||||||
"""Return current date string like 'Mar 25, 2026'"""
|
"""Return current date string like 'Mar 25, 2026' in display timezone."""
|
||||||
now_utc = datetime.now(timezone.utc)
|
now_utc = datetime.now(timezone.utc)
|
||||||
utc7 = timezone(timedelta(hours=7))
|
now_display = now_utc.astimezone(_DISPLAY_TZ)
|
||||||
now_utc7 = now_utc.astimezone(utc7)
|
return now_display.strftime("%b %d, %Y")
|
||||||
return now_utc7.strftime("%b %d, %Y")
|
|
||||||
|
|
||||||
|
|
||||||
def get_tournament(title: str) -> str:
|
def get_tournament(title: str) -> str:
|
||||||
@@ -1174,6 +1212,12 @@ def main() -> None:
|
|||||||
default=None,
|
default=None,
|
||||||
help="Max total events to fetch before early exit. Default: no limit.",
|
help="Max total events to fetch before early exit. Default: no limit.",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--timezone",
|
||||||
|
type=str,
|
||||||
|
default="UTC+7",
|
||||||
|
help="Timezone for displaying times (e.g., UTC+7, UTC-5). Default: UTC+7",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--telegram",
|
"--telegram",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@@ -1193,6 +1237,9 @@ def main() -> None:
|
|||||||
matches_max = args.matches if args.matches is not None else args.limit
|
matches_max = args.matches if args.matches is not None else args.limit
|
||||||
non_matches_max = args.non_matches if args.non_matches is not None else args.limit
|
non_matches_max = args.non_matches if args.non_matches is not None else args.limit
|
||||||
|
|
||||||
|
global _DISPLAY_TZ
|
||||||
|
_DISPLAY_TZ = parse_timezone(args.timezone)
|
||||||
|
|
||||||
if args.search:
|
if args.search:
|
||||||
print(f"\nFetching {args.category} events matching '{args.search}'...")
|
print(f"\nFetching {args.category} events matching '{args.search}'...")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -1839,5 +1839,48 @@ class TestBrowseEvents(unittest.TestCase):
|
|||||||
self.assertIn("partial", result)
|
self.assertIn("partial", result)
|
||||||
|
|
||||||
|
|
||||||
|
class TestTimezoneParsing(unittest.TestCase):
|
||||||
|
"""Tests for parse_timezone() and timezone display."""
|
||||||
|
|
||||||
|
def test_parse_timezone_utc_plus7(self):
|
||||||
|
"""UTC+7 should parse to WIB."""
|
||||||
|
from browse import parse_timezone
|
||||||
|
from datetime import timezone, timedelta
|
||||||
|
|
||||||
|
tz = parse_timezone("UTC+7")
|
||||||
|
self.assertEqual(tz, timezone(timedelta(hours=7)))
|
||||||
|
|
||||||
|
def test_parse_timezone_utc_minus5(self):
|
||||||
|
"""UTC-5 should parse correctly."""
|
||||||
|
from browse import parse_timezone
|
||||||
|
from datetime import timezone, timedelta
|
||||||
|
|
||||||
|
tz = parse_timezone("UTC-5")
|
||||||
|
self.assertEqual(tz, timezone(timedelta(hours=-5)))
|
||||||
|
|
||||||
|
def test_parse_timezone_utc_no_offset(self):
|
||||||
|
"""UTC should return timezone.utc."""
|
||||||
|
from browse import parse_timezone
|
||||||
|
|
||||||
|
tz = parse_timezone("UTC")
|
||||||
|
self.assertEqual(tz, timezone.utc)
|
||||||
|
|
||||||
|
def test_parse_timezone_with_minutes(self):
|
||||||
|
"""UTC+5:30 should parse correctly."""
|
||||||
|
from browse import parse_timezone
|
||||||
|
from datetime import timezone, timedelta
|
||||||
|
|
||||||
|
tz = parse_timezone("UTC+5:30")
|
||||||
|
self.assertEqual(tz, timezone(timedelta(hours=5, minutes=30)))
|
||||||
|
|
||||||
|
def test_parse_timezone_invalid_falls_back_to_wib(self):
|
||||||
|
"""Invalid timezone should fall back to WIB."""
|
||||||
|
from browse import parse_timezone
|
||||||
|
from datetime import timezone, timedelta
|
||||||
|
|
||||||
|
tz = parse_timezone("Invalid/Timezone")
|
||||||
|
self.assertEqual(tz, timezone(timedelta(hours=7)))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user