Skip to main content

Overview

The beaconcha.in API returns reward data indexed by epochs. This guide explains how to convert between epochs, Unix timestamps, and local time zones.
Helper Functions: This guide provides conversion functions used with /api/v2/ethereum/validators/rewards-list and other epoch-based endpoints.

Ethereum Time Constants

ConstantValueDescription
Genesis Timestamp1606824023December 1, 2020, 12:00:23 UTC
Seconds per Slot12Time between slots
Slots per Epoch32Slots in one epoch
Seconds per Epoch38432 × 12 seconds
Epochs per Day~22586,400 / 384
Epochs per Year~82,125365 × 225

Core Conversion Functions

from datetime import datetime, timezone, timedelta

GENESIS_TIMESTAMP = 1606824023
SECONDS_PER_EPOCH = 384

def epoch_to_timestamp(epoch: int) -> int:
    """Convert epoch number to Unix timestamp (start of epoch)."""
    return GENESIS_TIMESTAMP + (epoch * SECONDS_PER_EPOCH)

def timestamp_to_epoch(timestamp: int) -> int:
    """Convert Unix timestamp to epoch number."""
    return (timestamp - GENESIS_TIMESTAMP) // SECONDS_PER_EPOCH

def epoch_to_datetime(epoch: int, tz_offset_hours: int = 0) -> datetime:
    """Convert epoch to datetime in a specific time zone."""
    ts = epoch_to_timestamp(epoch)
    tz = timezone(timedelta(hours=tz_offset_hours))
    return datetime.fromtimestamp(ts, tz=tz)

def datetime_to_epoch(dt: datetime) -> int:
    """Convert datetime to epoch number."""
    return timestamp_to_epoch(int(dt.timestamp()))

# Examples
print(f"Epoch 0 = {epoch_to_datetime(0)}")
print(f"Epoch 400000 = {epoch_to_datetime(400000)}")
print(f"Epoch 400000 (CET) = {epoch_to_datetime(400000, 1)}")

Get Day Boundaries

Calculate which epochs correspond to a specific calendar day in your time zone:
from datetime import datetime, timezone, timedelta

GENESIS_TIMESTAMP = 1606824023
SECONDS_PER_EPOCH = 384

def timestamp_to_epoch(timestamp: int) -> int:
    return (timestamp - GENESIS_TIMESTAMP) // SECONDS_PER_EPOCH

def get_day_boundaries(year: int, month: int, day: int, tz_offset_hours: int = 0) -> dict:
    """
    Get epoch boundaries for a specific calendar day.
    
    Args:
        year, month, day: The date
        tz_offset_hours: Time zone offset from UTC
    """
    tz = timezone(timedelta(hours=tz_offset_hours))
    
    start_of_day = datetime(year, month, day, 0, 0, 0, tzinfo=tz)
    end_of_day = datetime(year, month, day, 23, 59, 59, tzinfo=tz)
    
    return {
        'date': f"{year}-{month:02d}-{day:02d}",
        'timezone': f"UTC{'+' if tz_offset_hours >= 0 else ''}{tz_offset_hours}",
        'start_epoch': timestamp_to_epoch(int(start_of_day.timestamp())),
        'end_epoch': timestamp_to_epoch(int(end_of_day.timestamp())),
        'epoch_count': timestamp_to_epoch(int(end_of_day.timestamp())) - 
                       timestamp_to_epoch(int(start_of_day.timestamp())) + 1
    }

# Example: December 15, 2025 in different time zones
print("December 15, 2025:")
for name, offset in [("UTC", 0), ("CET", 1), ("EST", -5), ("JST", 9)]:
    b = get_day_boundaries(2025, 12, 15, offset)
    print(f"  {name}: Epochs {b['start_epoch']:,} to {b['end_epoch']:,} ({b['epoch_count']} epochs)")
Output:
December 15, 2025:
  UTC: Epochs 412,359 to 412,583 (225 epochs)
  CET: Epochs 412,350 to 412,574 (225 epochs)
  EST: Epochs 412,372 to 412,596 (225 epochs)
  JST: Epochs 412,326 to 412,550 (225 epochs)

Get Year Boundaries

Calculate epoch boundaries for an entire calendar year:
from datetime import datetime, timezone, timedelta

GENESIS_TIMESTAMP = 1606824023
SECONDS_PER_EPOCH = 384

def timestamp_to_epoch(timestamp: int) -> int:
    return (timestamp - GENESIS_TIMESTAMP) // SECONDS_PER_EPOCH

def get_year_boundaries(year: int, tz_offset_hours: int = 0) -> dict:
    """Get epoch boundaries for an entire calendar year."""
    tz = timezone(timedelta(hours=tz_offset_hours))
    
    start_of_year = datetime(year, 1, 1, 0, 0, 0, tzinfo=tz)
    end_of_year = datetime(year, 12, 31, 23, 59, 59, tzinfo=tz)
    
    start_epoch = timestamp_to_epoch(int(start_of_year.timestamp()))
    end_epoch = timestamp_to_epoch(int(end_of_year.timestamp()))
    
    return {
        'year': year,
        'timezone': f"UTC{'+' if tz_offset_hours >= 0 else ''}{tz_offset_hours}",
        'start_epoch': start_epoch,
        'end_epoch': end_epoch,
        'total_epochs': end_epoch - start_epoch + 1,
        'start_date': start_of_year.strftime('%Y-%m-%d %H:%M:%S %Z'),
        'end_date': end_of_year.strftime('%Y-%m-%d %H:%M:%S %Z')
    }

# Example: Years 2021-2025 in UTC
print("Year boundaries (UTC):")
for year in range(2021, 2026):
    b = get_year_boundaries(year, 0)
    print(f"  {year}: Epochs {b['start_epoch']:,} to {b['end_epoch']:,} ({b['total_epochs']:,} epochs)")
Output:
Year boundaries (UTC):
  2021: Epochs 6,536 to 88,886 (82,351 epochs)
  2022: Epochs 88,887 to 170,886 (82,000 epochs)
  2023: Epochs 170,887 to 253,237 (82,351 epochs)
  2024: Epochs 253,238 to 335,587 (82,350 epochs)
  2025: Epochs 335,588 to 417,938 (82,351 epochs)

Time Zone Reference

RegionStandardDaylightUTC Offset
UKGMTBST0 / +1
Central EuropeCETCEST+1 / +2
Eastern EuropeEETEEST+2 / +3
US EasternESTEDT-5 / -4
US CentralCSTCDT-6 / -5
US PacificPSTPDT-8 / -7
JapanJST+9
ChinaCST+8
Australia EasternAESTAEDT+10 / +11
IndiaIST+5:30
For fractional time zones like India (+5:30), convert to minutes: tz_offset_minutes = 330 and adjust the calculation accordingly.

Verify API Response Times

When you receive a response from the rewards API, verify the epoch range matches your expectations:
import requests
from datetime import datetime, timezone

API_KEY = "<YOUR_API_KEY>"

# Fetch rewards
response = requests.post(
    "https://beaconcha.in/api/v2/ethereum/validators/rewards-aggregate",
    headers={"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"},
    json={
        "chain": "mainnet",
        "validator": {"dashboard_id": 123},
        "range": {"evaluation_window": "30d"}
    }
)

data = response.json()
range_info = data.get("range", {})

# Convert epoch range to dates
GENESIS_TIMESTAMP = 1606824023
SECONDS_PER_EPOCH = 384

start_epoch = range_info.get("epoch", {}).get("start")
end_epoch = range_info.get("epoch", {}).get("end")

start_ts = GENESIS_TIMESTAMP + (start_epoch * SECONDS_PER_EPOCH)
end_ts = GENESIS_TIMESTAMP + (end_epoch * SECONDS_PER_EPOCH)

print(f"Epoch range: {start_epoch:,} to {end_epoch:,}")
print(f"Start: {datetime.fromtimestamp(start_ts, tz=timezone.utc)}")
print(f"End: {datetime.fromtimestamp(end_ts, tz=timezone.utc)}")

For detailed API specifications, see the Rewards (CL + EL) section in the V2 API Docs sidebar.