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
| Constant | Value | Description |
|---|
| Genesis Timestamp | 1606824023 | December 1, 2020, 12:00:23 UTC |
| Seconds per Slot | 12 | Time between slots |
| Slots per Epoch | 32 | Slots in one epoch |
| Seconds per Epoch | 384 | 32 × 12 seconds |
| Epochs per Day | ~225 | 86,400 / 384 |
| Epochs per Year | ~82,125 | 365 × 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)}")
const GENESIS_TIMESTAMP = 1606824023;
const SECONDS_PER_EPOCH = 384;
function epochToTimestamp(epoch) {
return GENESIS_TIMESTAMP + (epoch * SECONDS_PER_EPOCH);
}
function timestampToEpoch(timestamp) {
return Math.floor((timestamp - GENESIS_TIMESTAMP) / SECONDS_PER_EPOCH);
}
function epochToDate(epoch, tzOffsetHours = 0) {
const ts = epochToTimestamp(epoch);
// Adjust for time zone
const date = new Date((ts + tzOffsetHours * 3600) * 1000);
return date;
}
function dateToEpoch(date) {
return timestampToEpoch(Math.floor(date.getTime() / 1000));
}
// Examples
console.log(`Epoch 0 = ${new Date(epochToTimestamp(0) * 1000).toISOString()}`);
console.log(`Epoch 400000 = ${new Date(epochToTimestamp(400000) * 1000).toISOString()}`);
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)
const GENESIS_TIMESTAMP = 1606824023;
const SECONDS_PER_EPOCH = 384;
function timestampToEpoch(timestamp) {
return Math.floor((timestamp - GENESIS_TIMESTAMP) / SECONDS_PER_EPOCH);
}
function getDayBoundaries(year, month, day, tzOffsetHours = 0) {
// month is 0-indexed in JavaScript Date
const startOfDay = new Date(Date.UTC(year, month - 1, day, -tzOffsetHours, 0, 0));
const endOfDay = new Date(Date.UTC(year, month - 1, day, 23 - tzOffsetHours, 59, 59));
const startEpoch = timestampToEpoch(Math.floor(startOfDay.getTime() / 1000));
const endEpoch = timestampToEpoch(Math.floor(endOfDay.getTime() / 1000));
return {
date: `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
timezone: `UTC${tzOffsetHours >= 0 ? '+' : ''}${tzOffsetHours}`,
startEpoch,
endEpoch,
epochCount: endEpoch - startEpoch + 1
};
}
// Example: December 15, 2025 in CET
const b = getDayBoundaries(2025, 12, 15, 1);
console.log(`${b.date} (${b.timezone}): Epochs ${b.startEpoch} to ${b.endEpoch}`);
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)
const GENESIS_TIMESTAMP = 1606824023;
const SECONDS_PER_EPOCH = 384;
function timestampToEpoch(timestamp) {
return Math.floor((timestamp - GENESIS_TIMESTAMP) / SECONDS_PER_EPOCH);
}
function getYearBoundaries(year, tzOffsetHours = 0) {
const startOfYear = new Date(Date.UTC(year, 0, 1, -tzOffsetHours, 0, 0));
const endOfYear = new Date(Date.UTC(year, 11, 31, 23 - tzOffsetHours, 59, 59));
const startEpoch = timestampToEpoch(Math.floor(startOfYear.getTime() / 1000));
const endEpoch = timestampToEpoch(Math.floor(endOfYear.getTime() / 1000));
return {
year,
startEpoch,
endEpoch,
totalEpochs: endEpoch - startEpoch + 1
};
}
// Example
for (let year = 2021; year <= 2025; year++) {
const b = getYearBoundaries(year, 0);
console.log(`${year}: Epochs ${b.startEpoch.toLocaleString()} to ${b.endEpoch.toLocaleString()}`);
}
Time Zone Reference
| Region | Standard | Daylight | UTC Offset |
|---|
| UK | GMT | BST | 0 / +1 |
| Central Europe | CET | CEST | +1 / +2 |
| Eastern Europe | EET | EEST | +2 / +3 |
| US Eastern | EST | EDT | -5 / -4 |
| US Central | CST | CDT | -6 / -5 |
| US Pacific | PST | PDT | -8 / -7 |
| Japan | JST | — | +9 |
| China | CST | — | +8 |
| Australia Eastern | AEST | AEDT | +10 / +11 |
| India | IST | — | +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.