Developer Guide
Complete API reference and integration guide for module developers working with Seasons & Stars.
📚 Table of Contents
- Getting Started
- Core API
- Simple Calendar Compatibility
- Hook System
- Calendar Data Structures
- Integration Examples
- Migration Guide
- Best Practices
🚀 Getting Started
API Access
Seasons & Stars exposes its API through the global game
object:
// Check if Seasons & Stars is available
if (game.seasonsStars) {
const currentDate = game.seasonsStars.api.getCurrentDate();
console.log('Current date:', currentDate);
}
Detecting Seasons & Stars
// Method 1: Direct check
const hasSeasonsStars = !!game.seasonsStars;
// Method 2: Feature detection
const hasAdvancedCalendar = typeof game.seasonsStars?.api?.getCurrentDate === 'function';
// Method 3: Version check (when available)
const version = game.seasonsStars?.VERSION;
🔧 Core API
Date Retrieval
getCurrentDate()
Get the current date from the active calendar.
// Get current date from active calendar
const currentDate = game.seasonsStars.api.getCurrentDate();
// Returns: { year: 2024, month: 12, day: 25, weekday: 3, time: { hour: 14, minute: 30, second: 0 } }
The calendarId
parameter is planned for future implementation but not currently supported. All operations use the currently active calendar.
Returns: CalendarDate | null
Time Manipulation
advanceDays(days: number)
Advance world time by specified number of days.
// Advance by 1 day
await game.seasonsStars.api.advanceDays(1);
// Go back 3 days (negative values)
await game.seasonsStars.api.advanceDays(-3);
advanceHours(hours: number)
Advance world time by specified number of hours.
// Advance by 8 hours (long rest)
await game.seasonsStars.api.advanceHours(8);
advanceMinutes(minutes: number)
Advance world time by specified number of minutes.
// Advance by 30 minutes
await game.seasonsStars.api.advanceMinutes(30);
Other Time Functions
// Time advancement functions
await game.seasonsStars.api.advanceMinutes(minutes);
await game.seasonsStars.api.advanceWeeks(weeks);
await game.seasonsStars.api.advanceMonths(months);
await game.seasonsStars.api.advanceYears(years);
Calendar Metadata
getMonthNames()
Get array of month names for the active calendar.
// Get month names from active calendar
const months = game.seasonsStars.api.getMonthNames();
// Returns: ["January", "February", "March", ...]
Returns: string[]
getWeekdayNames()
Get array of weekday names for the active calendar.
// Get weekday names from active calendar
const weekdays = game.seasonsStars.api.getWeekdayNames();
// Returns: ["Sunday", "Monday", "Tuesday", ...]
Returns: string[]
Enhanced Features
getSunriseSunset(date: CalendarDate)
Get sunrise and sunset times for a specific date.
const currentDate = game.seasonsStars.api.getCurrentDate();
const sunTimes = game.seasonsStars.api.getSunriseSunset(currentDate);
// Returns: { sunrise: 6, sunset: 18 }
Returns: { sunrise: number; sunset: number }
Currently returns default values (6 AM sunrise, 6 PM sunset). This can be enhanced in future versions with calendar-specific astronomical calculations.
getSeasonInfo(date: CalendarDate)
Get season information for a specific date.
const currentDate = game.seasonsStars.api.getCurrentDate();
const season = game.seasonsStars.api.getSeasonInfo(currentDate);
// Returns: { name: "Winter", icon: "winter" }
Returns: { name: string; icon: string }
Currently uses simple month-range matching to determine seasons. Future versions may include more sophisticated season calculation based on calendar-specific rules.
Date Conversion
dateToWorldTime(date: CalendarDate)
Convert a calendar date to Foundry world time.
const date = {
year: 2024,
month: 12,
day: 25,
weekday: 3,
time: { hour: 12, minute: 0, second: 0 }
};
const worldTime = game.seasonsStars.api.dateToWorldTime(date);
console.log('World time:', worldTime); // Returns timestamp in seconds
worldTimeToDate(timestamp: number)
Convert Foundry world time to calendar date.
const currentTime = game.time.worldTime;
const date = game.seasonsStars.api.worldTimeToDate(currentTime);
console.log('Calendar date:', date);
Date Formatting
formatDate(date: CalendarDate, options?: DateFormatOptions)
Format a date according to calendar conventions.
const date = game.seasonsStars.api.getCurrentDate();
// Default formatting
const formatted = game.seasonsStars.api.formatDate(date);
// Returns: "December 25th, 2024 CE"
// Custom formatting options
const custom = game.seasonsStars.api.formatDate(date, {
includeTime: true,
includeWeekday: true,
format: 'long'
});
// Returns: "Wednesday, December 25th, 2024 CE at 2:30 PM"
Options:
interface DateFormatOptions {
includeTime?: boolean;
includeWeekday?: boolean;
includeYear?: boolean;
format?: 'short' | 'long' | 'numeric';
}
Calendar Management
getActiveCalendar()
Get the currently active calendar definition.
const calendar = game.seasonsStars.api.getActiveCalendar();
console.log('Active calendar:', calendar.id);
console.log('Months:', calendar.months.map(m => m.name));
setActiveCalendar(calendarId: string)
Switch to a different calendar system.
// Switch to Vale Reckoning calendar
await game.seasonsStars.api.setActiveCalendar('vale-reckoning');
// Switch back to Gregorian
await game.seasonsStars.api.setActiveCalendar('gregorian');
getAvailableCalendars()
Get list of all available calendar IDs.
const calendars = game.seasonsStars.api.getAvailableCalendars();
console.log('Available calendars:', calendars);
// Returns: ['gregorian', 'vale-reckoning', 'custom-calendar']
🔄 Simple Calendar Compatibility
Seasons & Stars provides a clean integration interface that enables compatibility with Simple Calendar-dependent modules through the Simple Calendar Compatibility Bridge.
In future versions, Seasons & Stars will provide a clean integration interface that the separate Simple Calendar Compatibility Bridge module uses to provide Simple Calendar API compatibility.
Integration Interface
Seasons & Stars exposes a standardized integration interface that compatibility bridges can use:
// Check if S&S integration interface is available
const integration = game.seasonsStars?.integration;
if (integration?.isAvailable) {
console.log('S&S Integration Interface available');
console.log('API methods:', Object.keys(integration.api));
console.log('Widget support:', !!integration.widgets);
console.log('Hook support:', !!integration.hooks);
}
For Simple Calendar Compatibility
To use Simple Calendar-dependent modules with Seasons & Stars:
- Install Seasons & Stars (this module)
- Install Simple Calendar Compatibility Bridge
- Install your Simple Calendar-dependent modules (Simple Weather, SmallTime, etc.)
The compatibility bridge provides Simple Calendar API compatibility using S&S as the underlying calendar system.
Integration Interface API
The integration interface provides these methods for compatibility bridges:
const integration = game.seasonsStars.integration;
// Core date operations
const currentDate = integration.api.getCurrentDate();
const convertedDate = integration.api.worldTimeToDate(timestamp);
const timestamp = integration.api.dateToWorldTime(date);
// Calendar metadata (newly implemented)
const months = integration.api.getMonthNames();
const weekdays = integration.api.getWeekdayNames();
// Enhanced features (basic implementations)
const sunTimes = integration.api.getSunriseSunset(date);
const season = integration.api.getSeasonInfo(date);
// Widget integration ✅
if (integration.widgets.mini) {
integration.widgets.mini.addSidebarButton('myButton', 'fas fa-star', 'My Button', callback);
integration.widgets.mini.removeSidebarButton('myButton'); // ✅ Implemented
const hasButton = integration.widgets.mini.hasSidebarButton('myButton'); // ✅ Implemented
}
// Hook integration
integration.hooks.onDateChanged((event) => {
console.log('Date changed:', event.newDate);
});
Use the integration interface rather than the direct S&S API when building compatibility modules. This ensures your module works consistently across different S&S versions.
Widget Integration API ✅
Seasons & Stars provides a widget system for adding custom buttons and integrations:
interface SeasonsStarsWidgets {
addSidebarButton(name: string, icon: string, tooltip: string, callback: Function): void; // ✅ Implemented
removeSidebarButton(name: string): void; // ✅ Implemented
hasSidebarButton(name: string): boolean; // ✅ Implemented
show(): void; // ✅ Implemented
hide(): void; // ✅ Implemented
toggle(): void; // ✅ Implemented
}
Usage Example:
// Add a custom button to calendar widget
game.seasonsStars.widgets.main.addSidebarButton(
'weather',
'fas fa-cloud-sun',
'Toggle Weather',
() => WeatherApp.toggle()
);
// Add button to mini widget
game.seasonsStars.widgets.mini.addSidebarButton(
'notes',
'fas fa-sticky-note',
'Quick Note',
() => createQuickNote()
);
// Remove button when no longer needed
game.seasonsStars.widgets.main.removeSidebarButton('weather');
// Check if button exists
if (game.seasonsStars.widgets.mini.hasSidebarButton('notes')) {
console.log('Notes button is active');
}
🪝 Hook System ✅
Seasons & Stars Hooks ✅
seasons-stars:dateChanged
Fired when the world time changes.
Hooks.on('seasons-stars:dateChanged', (data) => {
console.log('Date changed from', data.oldTime, 'to', data.newTime);
console.log('New date:', data.newDate);
console.log('Time delta:', data.delta, 'seconds');
// Update your module's time-sensitive features
updateWeatherForNewDate(data.newDate);
refreshTimedAbilities();
});
Data Structure:
interface DateChangeData {
newDate: CalendarDate;
oldTime: number;
newTime: number;
delta: number;
}
seasons-stars:calendarChanged
Fired when the active calendar system changes.
Hooks.on('seasons-stars:calendarChanged', (data) => {
console.log('Calendar changed to:', data.newCalendarId);
console.log('New calendar data:', data.calendar);
// Recalculate seasonal effects, holidays, etc.
recalculateSeasonalEffects(data.calendar);
});
seasons-stars:ready
Fired when Seasons & Stars is fully initialized.
Hooks.on('seasons-stars:ready', (data) => {
console.log('Seasons & Stars ready');
console.log('Manager:', data.manager);
console.log('API:', data.api);
// Safe to use Seasons & Stars API
initializeCalendarIntegration();
});
seasons-stars:registerExternalCalendars
(Added in v0.8.0)
Fired during initialization to allow modules to register calendars programmatically. This provides an alternative to calendar packs for dynamic calendar registration.
Hooks.on('seasons-stars:registerExternalCalendars', ({ registerCalendar, manager }) => {
// Register a custom calendar
const myCalendar = {
id: 'my-custom-calendar',
name: 'My Custom Calendar',
months: [
{ name: 'First Month', days: 30 },
{ name: 'Second Month', days: 31 }
],
weekdays: ['Day1', 'Day2', 'Day3', 'Day4', 'Day5'],
// ... full calendar definition
};
const sourceInfo = {
name: 'My Calendar Module',
version: '1.0.0',
type: 'external'
};
const success = registerCalendar(myCalendar, sourceInfo);
console.log('Calendar registered:', success);
// Register multiple calendars
calendarsToRegister.forEach(calendar => {
registerCalendar(calendar, sourceInfo);
});
});
Registration Callback Parameters:
interface ExternalCalendarRegistration {
registerCalendar: (calendar: CalendarDefinition, source: SourceInfo) => boolean;
manager: CalendarManager;
}
interface SourceInfo {
name: string; // Module/source name
version: string; // Module version
type: 'external'; // Source type identifier
}
When to Use:
- Calendar Packs: Best for distributable collections, auto-detection, no JavaScript required
- External Registration Hook: Best for runtime registration, dynamic calendars, programmatic control
Debug and Development Hooks ✅
Seasons & Stars provides additional hooks for debugging and development:
seasons-stars:noteCreated
✅
Fired when a calendar note is created.
Hooks.on('seasons-stars:noteCreated', (data) => {
console.log('Note created:', data.note);
console.log('Date:', data.date);
console.log('Created by:', data.userId);
// Integrate with other modules
updateWeatherForNote(data.note, data.date);
});
Data Structure:
interface NoteCreatedData {
note: CalendarNote;
date: CalendarDate;
userId: string;
}
Simple Calendar Hook Compatibility
Mapped Hooks
// These hooks are automatically mapped for compatibility:
Hooks.on(SimpleCalendar.Hooks.DateTimeChange, (data) => {
// Equivalent to 'seasons-stars:dateChanged'
});
Hooks.on(SimpleCalendar.Hooks.Ready, (data) => {
// Equivalent to 'seasons-stars:ready'
});
Available Debug APIs ✅
Seasons & Stars exposes several debug and diagnostic APIs:
game.seasonsStars.manager.getDebugInfo()
✅
Get comprehensive debug information about the calendar system.
const debugInfo = game.seasonsStars.manager.getDebugInfo();
console.log('Calendar Engine State:', debugInfo.engine);
console.log('Current Date:', debugInfo.currentDate);
console.log('Active Calendar:', debugInfo.activeCalendar);
console.log('World Time:', debugInfo.worldTime);
console.log('Conversion Test:', debugInfo.conversionTest);
game.seasonsStars.categories
🚧
Access the note categories system (when notes system is enabled).
// Get all available categories
const categories = game.seasonsStars.categories?.getAll();
console.log('Available categories:', categories);
// Add custom category
game.seasonsStars.categories?.addCategory({
id: 'custom',
name: 'Custom Events',
color: '#ff6b6b',
icon: 'fas fa-star'
});
The notes system including categories is fully implemented but may not be exposed in all S&S versions. Check for availability before use.
Calendar Validation APIs ✅
// Validate calendar definition
const validation = game.seasonsStars.manager.validateAllCalendars();
console.log('Calendar validation results:', validation);
// Get detailed validation for specific calendar
const detailedValidation = game.seasonsStars.manager.getActiveEngine().validateWithHelp();
console.log('Detailed validation:', detailedValidation);
📊 Calendar Data Structures
CalendarDate Interface
interface CalendarDate {
year: number;
month: number; // 1-based (1 = first month)
day: number; // 1-based (1 = first day)
weekday: number; // 0-based (0 = first weekday)
intercalary?: string; // Special day name (optional)
time?: {
hour: number; // 0-23
minute: number; // 0-59
second: number; // 0-59
};
}
Calendar Structure
interface SeasonsStarsCalendar {
id: string;
translations: {
[languageCode: string]: {
label: string;
description?: string;
setting?: string;
};
};
year: {
epoch: number; // Starting year for calculations
currentYear: number; // Default current year
prefix: string; // Text before year (e.g., "")
suffix: string; // Text after year (e.g., " CE")
startDay: number; // Which weekday the epoch starts on
};
months: CalendarMonth[];
weekdays: CalendarWeekday[];
intercalary: CalendarIntercalary[]; // Special days
leapYear: {
rule: 'none' | 'gregorian' | 'custom';
interval?: number; // For custom rules
month?: string; // Which month gets extra days
extraDays?: number; // How many extra days
};
time: {
hoursInDay: number; // Usually 24
minutesInHour: number; // Usually 60
secondsInMinute: number; // Usually 60
};
}
🔧 Integration Examples
Weather Module Integration
class WeatherManager {
constructor() {
this.setupCalendarIntegration();
}
setupCalendarIntegration() {
// Support both Simple Calendar and Seasons & Stars
if (window.SimpleCalendar) {
Hooks.on(SimpleCalendar.Hooks.DateTimeChange, this.onDateChange.bind(this));
this.calendarAPI = SimpleCalendar.api;
}
// Direct Seasons & Stars integration (preferred)
if (game.seasonsStars) {
Hooks.on('seasons-stars:dateChanged', this.onDateChange.bind(this));
this.calendarAPI = game.seasonsStars.api;
}
}
onDateChange(data) {
const currentDate = this.calendarAPI.getCurrentDate();
const season = this.calculateSeason(currentDate);
const weather = this.generateWeather(season, currentDate);
this.updateWeatherDisplay(weather);
this.saveWeatherToNotes(currentDate, weather);
}
calculateSeason(date) {
// Use calendar months to determine season
const calendar = this.calendarAPI.getActiveCalendar();
const monthsPerSeason = calendar.months.length / 4;
return Math.floor((date.month - 1) / monthsPerSeason);
}
async saveWeatherToNotes(date, weather) {
// Method 1: Simple Calendar compatibility (via bridge)
if (this.calendarAPI.addNote) {
await this.calendarAPI.addNote({
date: date,
title: 'Weather',
content: weather.description,
category: 'weather'
});
}
// Method 2: Direct S&S notes API ✅ (when available)
if (game.seasonsStars.notes) {
await game.seasonsStars.notes.createNote({
title: 'Daily Weather',
content: weather.description,
startDate: date,
category: 'weather',
allDay: true,
playerVisible: true
});
}
// Method 3: Module flag storage ✅ (always available)
if (game.seasonsStars.notes?.setNoteModuleData) {
await game.seasonsStars.notes.setNoteModuleData(
'weather-data',
`weather-${date.year}-${date.month}-${date.day}`,
{
temperature: weather.temperature,
conditions: weather.conditions,
windSpeed: weather.windSpeed,
timestamp: Date.now()
}
);
}
}
}
Time-Sensitive Spell Effects
class SpellEffectManager {
constructor() {
Hooks.on('seasons-stars:dateChanged', this.checkExpiringEffects.bind(this));
}
async addTimedEffect(actorId, effectData, duration) {
const currentDate = game.seasonsStars.api.getCurrentDate();
const expirationTime = game.time.worldTime + duration;
// Store expiration time with effect
const effect = {
...effectData,
expirationTime: expirationTime,
startDate: currentDate
};
await this.storeEffect(actorId, effect);
}
checkExpiringEffects(data) {
const currentTime = data.newTime;
// Check all active effects
for (const [actorId, effects] of this.activeEffects) {
const expired = effects.filter(e => e.expirationTime <= currentTime);
expired.forEach(effect => {
this.removeEffect(actorId, effect);
ui.notifications.info(`${effect.name} effect has expired on ${actorId}`);
});
}
}
}
Calendar Event System
class EventManager {
constructor() {
this.events = new Map();
this.setupEventHandling();
}
setupEventHandling() {
Hooks.on('seasons-stars:dateChanged', this.checkEvents.bind(this));
Hooks.on('seasons-stars:calendarChanged', this.convertEventDates.bind(this));
}
addRecurringEvent(eventData) {
const event = {
id: foundry.utils.randomID(),
name: eventData.name,
description: eventData.description,
recurrence: eventData.recurrence, // 'daily', 'weekly', 'monthly', 'yearly'
startDate: eventData.startDate,
endDate: eventData.endDate
};
this.events.set(event.id, event);
}
checkEvents(data) {
const currentDate = data.newDate;
for (const event of this.events.values()) {
if (this.shouldTriggerEvent(event, currentDate)) {
this.triggerEvent(event, currentDate);
}
}
}
shouldTriggerEvent(event, currentDate) {
// Check if event should trigger on current date
switch (event.recurrence) {
case 'daily':
return true;
case 'weekly':
return currentDate.weekday === event.startDate.weekday;
case 'monthly':
return currentDate.day === event.startDate.day;
case 'yearly':
return currentDate.month === event.startDate.month &&
currentDate.day === event.startDate.day;
default:
return false;
}
}
}
🔄 Migration Guide
From Simple Calendar
Phase 1: Basic Compatibility
Your existing Simple Calendar integration should work immediately:
// This code continues working unchanged:
const currentDate = SimpleCalendar.api.currentDateTime();
const formatted = SimpleCalendar.api.timestampToDate(game.time.worldTime);
Hooks.on(SimpleCalendar.Hooks.DateTimeChange, (data) => {
// Your existing hook handler
});
Phase 2: Enhanced Integration
Migrate to native Seasons & Stars API for better features:
// Old Simple Calendar code:
const oldDate = SimpleCalendar.api.currentDateTime();
// New Seasons & Stars code:
const newDate = game.seasonsStars.api.getCurrentDate();
// Benefits: Better type safety, more features, direct access
Phase 3: Notes System Migration
When Phase 2 releases, migrate note handling:
// Old Simple Calendar notes:
const notes = SimpleCalendar.api.getNotes(date);
// New Seasons & Stars notes:
const notes = game.seasonsStars.api.getNotes(date);
Notes System API ✅
If the notes system is available, you can use the comprehensive notes API:
// Check if notes system is available
if (game.seasonsStars.notes) {
// Create a note
const note = await game.seasonsStars.notes.createNote({
title: 'Important Event',
content: 'Something significant happens today.',
startDate: game.seasonsStars.api.getCurrentDate(),
category: 'event',
allDay: true,
playerVisible: true
});
// Find notes for a specific date
const currentDate = game.seasonsStars.api.getCurrentDate();
const todaysNotes = await game.seasonsStars.notes.findNotesByDate(currentDate);
// Store module-specific data
await game.seasonsStars.notes.setNoteModuleData(
note.id,
'my-module',
{ customData: 'module specific information' }
);
// Retrieve module data
const moduleData = await game.seasonsStars.notes.getNoteModuleData(
note.id,
'my-module'
);
}
The notes system is implemented in S&S core with CRUD operations, categories, search functionality, and module flag support. It provides Simple Calendar notes API compatibility.
From Custom Time Systems
Step 1: Replace Time Calculations
// Old custom time code:
function advanceGameTime(hours) {
const newTime = game.time.worldTime + (hours * 3600);
game.time.advance(newTime - game.time.worldTime);
}
// New Seasons & Stars code:
async function advanceGameTime(hours) {
await game.seasonsStars.api.advanceHours(hours);
}
Step 2: Use Calendar-Aware Functions
// Old date calculation:
function addDaysToDate(date, days) {
// Complex calculation considering month lengths, leap years, etc.
}
// New calendar-aware calculation:
function addDaysToDate(date, days) {
return game.seasonsStars.api.dateToWorldTime(
game.seasonsStars.manager.getActiveEngine().addDays(date, days)
);
}
🎯 Best Practices
Error Handling
// Always check if Seasons & Stars is available
function safeGetCurrentDate() {
if (!game.seasonsStars?.api?.getCurrentDate) {
console.warn('Seasons & Stars not available');
return null;
}
try {
return game.seasonsStars.api.getCurrentDate();
} catch (error) {
console.error('Failed to get current date:', error);
return null;
}
}
Performance Considerations
// Cache calendar data instead of repeated API calls
class CalendarCache {
constructor() {
this.calendarData = null;
this.lastUpdate = 0;
Hooks.on('seasons-stars:calendarChanged', () => {
this.invalidateCache();
});
}
getCalendar() {
const now = Date.now();
if (!this.calendarData || now - this.lastUpdate > 60000) {
this.calendarData = game.seasonsStars.api.getActiveCalendar();
this.lastUpdate = now;
}
return this.calendarData;
}
invalidateCache() {
this.calendarData = null;
this.lastUpdate = 0;
}
}
Graceful Degradation
// Support multiple calendar systems
class UniversalCalendarAdapter {
constructor() {
this.adapter = this.detectCalendarSystem();
}
detectCalendarSystem() {
if (game.seasonsStars) {
return new SeasonsStarsAdapter();
} else if (window.SimpleCalendar) {
return new SimpleCalendarAdapter();
} else {
return new FallbackAdapter();
}
}
getCurrentDate() {
return this.adapter.getCurrentDate();
}
onDateChange(callback) {
this.adapter.onDateChange(callback);
}
}
Module Dependencies
// In your module.json
{
"relationships": {
"systems": [],
"requires": [],
"recommends": [
{
"id": "seasons-and-stars",
"type": "module",
"reason": "Enhanced calendar functionality"
}
]
}
}
Need more help? Check the User Guide for basic usage or visit our GitHub Discussions for developer support.