Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/trustlessmatt/discord-exporter-bot/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Discord Exporter Bot uses Eastern Time (America/New_York) for all timestamps in exports and digests. This provides consistent, location-aware timestamps with automatic daylight saving time (DST) handling using Python’s zoneinfo module.

Why Eastern Time?

The bot defaults to Eastern Time for several reasons:
  1. Team location: Designed for teams primarily in US Eastern timezone
  2. Business hours alignment: Digest generation at midnight ET matches US business context
  3. DST awareness: Automatically handles EST ↔ EDT transitions
  4. Consistency: Single timezone for all exports prevents confusion
  5. Configurable: Can be changed in the Config class if needed

Timezone Conversion Function

The core conversion function is simple but powerful:
def convert_to_eastern(utc_time: datetime, tz: ZoneInfo) -> datetime:
    """Convert UTC datetime to target timezone (handles DST automatically)."""
    if utc_time.tzinfo is None:
        utc_time = utc_time.replace(tzinfo=timezone.utc)
    return utc_time.astimezone(tz)
How it works:
  1. Check for timezone info: If the datetime is naive (no timezone), assume UTC
  2. Add UTC timezone: Attach timezone.utc to the datetime
  3. Convert: Use astimezone() to convert to target timezone
  4. DST automatic: Python’s zoneinfo handles DST transitions automatically

Example Usage

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

# Discord provides UTC timestamps
utc_time = datetime(2026, 3, 4, 19, 30, 0, tzinfo=timezone.utc)

# Convert to Eastern Time
eastern_tz = ZoneInfo("America/New_York")
eastern_time = convert_to_eastern(utc_time, eastern_tz)

print(utc_time.isoformat())
# Output: 2026-03-04T19:30:00+00:00

print(eastern_time.isoformat())
# Output: 2026-03-04T14:30:00-05:00 (EST in winter)
# Output: 2026-03-04T15:30:00-04:00 (EDT in summer)

ZoneInfo Usage

The bot uses Python 3.9+ zoneinfo module for IANA timezone database support:
from zoneinfo import ZoneInfo

# In Config class (bot.py:30)
eastern_tz: ZoneInfo = field(default_factory=lambda: ZoneInfo("America/New_York"))
Advantages of ZoneInfo:
  • IANA database: Uses official timezone data (same as Linux/macOS)
  • DST automatic: Knows when to switch between EST and EDT
  • Historical accuracy: Handles timezone rule changes over time
  • No external dependencies: Built into Python 3.9+

Supported Timezone Names

# North America
ZoneInfo("America/New_York")      # Eastern Time (ET)
ZoneInfo("America/Chicago")       # Central Time (CT)
ZoneInfo("America/Denver")        # Mountain Time (MT)
ZoneInfo("America/Los_Angeles")   # Pacific Time (PT)

# Europe
ZoneInfo("Europe/London")         # GMT/BST
ZoneInfo("Europe/Paris")          # CET/CEST

# Asia
ZoneInfo("Asia/Tokyo")            # JST (no DST)
ZoneInfo("Asia/Kolkata")          # IST (no DST)
To change timezone, edit Config class:
# For Pacific Time
eastern_tz: ZoneInfo = field(default_factory=lambda: ZoneInfo("America/Los_Angeles"))

# For UTC (no conversion)
eastern_tz: ZoneInfo = field(default_factory=lambda: ZoneInfo("UTC"))

Automatic DST Handling

Daylight Saving Time transitions happen automatically without any code changes:

EST to EDT Transition (Spring Forward)

# March 9, 2026 at 2:00 AM EST → 3:00 AM EDT

# Before DST (1:59 AM EST)
utc_time = datetime(2026, 3, 9, 6, 59, 0, tzinfo=timezone.utc)
eastern = convert_to_eastern(utc_time, ZoneInfo("America/New_York"))
print(eastern)  # 2026-03-09 01:59:00-05:00 (EST)

# After DST (3:00 AM EDT)
utc_time = datetime(2026, 3, 9, 7, 0, 0, tzinfo=timezone.utc)
eastern = convert_to_eastern(utc_time, ZoneInfo("America/New_York"))
print(eastern)  # 2026-03-09 03:00:00-04:00 (EDT)

EDT to EST Transition (Fall Back)

# November 1, 2026 at 2:00 AM EDT → 1:00 AM EST

# Before DST ends (1:59 AM EDT)
utc_time = datetime(2026, 11, 1, 5, 59, 0, tzinfo=timezone.utc)
eastern = convert_to_eastern(utc_time, ZoneInfo("America/New_York"))
print(eastern)  # 2026-11-01 01:59:00-04:00 (EDT)

# After DST ends (1:00 AM EST)
utc_time = datetime(2026, 11, 1, 6, 0, 0, tzinfo=timezone.utc)
eastern = convert_to_eastern(utc_time, ZoneInfo("America/New_York"))
print(eastern)  # 2026-11-01 01:00:00-05:00 (EST)
Key points:
  • The bot code never needs to know about DST transitions
  • ZoneInfo handles all offset calculations automatically
  • Works correctly across DST boundaries in exports
  • Historical accuracy for old messages

Timestamp Formats

The bot uses different timestamp formats in different contexts:

ISO 8601 Format (Exports)

# In serialize_message function (bot.py:116-152)
eastern_time = convert_to_eastern(message.created_at, config.eastern_tz)

return {
    "timestamp": eastern_time.isoformat(),
    "timestamp_utc": message.created_at.isoformat()
}
Output:
{
  "timestamp": "2026-03-04T14:23:15-05:00",
  "timestamp_utc": "2026-03-04T19:23:15+00:00"
}
Format breakdown:
  • 2026-03-04 - Date (YYYY-MM-DD)
  • T - Time separator
  • 14:23:15 - Time (HH:MM:SS)
  • -05:00 - UTC offset (EST is -5 hours)
  • -04:00 - UTC offset during EDT

Human-Readable Format (Digests)

# In prepare_transcript function (bot.py:268-284)
messages_text = "\n".join([
    f"[{msg['timestamp'][:19]}] {msg['author']['display_name']}: {msg['content_clean']}"
    for msg in non_bot_messages
])
Output:
[2026-03-04T14:23:15] John Doe: Finished the authentication refactor
Format: Truncated to 19 characters (removes timezone offset)

12-Hour Format (Footers)

# In format_obsidian_document function (bot.py:341-374)
*Auto-generated at {datetime.now(config.eastern_tz).strftime('%I:%M %p ET')} from Discord*
Output:
Auto-generated at 12:00 AM ET from Discord
Auto-generated at 02:30 PM ET from Discord
Format string:
  • %I - 12-hour (01-12)
  • %M - Minutes (00-59)
  • %p - AM/PM
  • ET - Literal “ET” suffix

Timezone in Export Metadata

Every export includes timezone information:
# In perform_export function (bot.py:204-256)
export_data = {
    "guild_name": guild.name,
    "export_time_eastern": export_time_eastern.isoformat(),
    "export_time_utc": export_time_utc.isoformat(),
    "timezone": "America/New_York (Eastern Time - auto DST)",
    "time_range_hours": hours,
    "channels": {}
}
Example output:
{
  "guild_name": "My Discord Server",
  "export_time_eastern": "2026-03-04T14:30:00-05:00",
  "export_time_utc": "2026-03-04T19:30:00+00:00",
  "timezone": "America/New_York (Eastern Time - auto DST)",
  "time_range_hours": 24
}
Why both timestamps:
  • Eastern Time: For human consumption and local context
  • UTC: For programmatic processing and international teams
  • Dual format: Ensures no information loss

Scheduled Task Timing

The daily digest runs at midnight Eastern Time:
# From Config class (bot.py:39)
scheduled_hour: int = 0  # 12am ET

# From before_daily_digest function (bot.py:672-686)
async def before_daily_digest():
    """Wait until scheduled time to start the loop."""
    await bot.wait_until_ready()

    now = datetime.now(config.eastern_tz)
    target_time = now.replace(hour=config.scheduled_hour, minute=0, second=0, microsecond=0)

    if now.time() >= target_time.time():
        target_time += timedelta(days=1)

    wait_seconds = (target_time - now).total_seconds()
    logger.info(f"⏰ Waiting {wait_seconds/3600:.1f} hours until first digest at {config.scheduled_hour}:00am ET")
    await asyncio.sleep(wait_seconds)
How it works:
  1. Get current time in ET: now = datetime.now(config.eastern_tz)
  2. Set target to midnight: target_time = now.replace(hour=0, ...)
  3. If past midnight today, target tomorrow: Check if now >= target_time
  4. Calculate wait time: Seconds until next midnight
  5. Sleep until midnight: asyncio.sleep(wait_seconds)
DST behavior:
  • On “spring forward” day: Digest runs at 12:00 AM EDT (1 hour earlier in UTC)
  • On “fall back” day: Digest runs at 12:00 AM EST (1 hour later in UTC)
  • Users always see “midnight ET” in their local timezone context

Message Timestamp Examples

Here’s how timestamps appear in different parts of the system:

In JSON Export

{
  "message_id": "1234567890",
  "author": {"name": "john_doe", "display_name": "John Doe", "id": "111222333", "bot": false},
  "content": "Just deployed to production!",
  "timestamp": "2026-03-04T14:23:15-05:00",
  "timestamp_utc": "2026-03-04T19:23:15+00:00"
}

In Claude Transcript

[2026-03-04T14:23:15] John Doe: Just deployed to production!
[2026-03-04T14:25:42] Jane Smith: Great work!
[2026-03-04T14:30:18] Mike Johnson: Running smoke tests now

In Obsidian Digest

---
date: 2026-03-04
---

# Team Digest - 2026-03-04

[Content here]

---
*Auto-generated at 12:00 AM ET from Discord*

Changing the Timezone

To use a different timezone, modify the Config class:
# bot.py:30
@dataclass
class Config:
    # ... other fields ...
    
    # Change this line:
    eastern_tz: ZoneInfo = field(default_factory=lambda: ZoneInfo("America/Los_Angeles"))
    
    # For UTC (no conversion):
    eastern_tz: ZoneInfo = field(default_factory=lambda: ZoneInfo("UTC"))
    
    # For London:
    eastern_tz: ZoneInfo = field(default_factory=lambda: ZoneInfo("Europe/London"))
Update these locations:
  1. Config class default (bot.py:30)
  2. Timezone string in export metadata (bot.py:220)
  3. Timestamp format strings in documentation

Timezone Validation

The bot doesn’t explicitly validate timezone names, but ZoneInfo will raise an exception if invalid:
try:
    tz = ZoneInfo("Invalid/Timezone")
except ZoneInfoNotFoundError:
    print("Timezone not found in IANA database")
Valid timezone list:
# On Linux/macOS
ls /usr/share/zoneinfo/

# In Python
import zoneinfo
print(zoneinfo.available_timezones())

Performance Considerations

ZoneInfo is efficient:
  • Timezone objects are cached after first use
  • Conversion is O(1) operation
  • No external API calls or network requests
  • Timezone data loaded from system database
No performance impact from:
  • Converting thousands of message timestamps
  • DST boundary calculations
  • Historical date conversions

Build docs developers (and LLMs) love