# How to Use fabrica-util's xtime Package for Advanced Timezone Management and Period Calculations in Game Servers

> Master timezone management in game servers with fabrica-util xtime. Easily schedule events and convert player timestamps using NextDailyTime and InTimezone for efficient period calculations.

- Repository: [Pantheon/fabrica-util](https://github.com/go-pantheon/fabrica-util)
- Tags: tutorial
- Published: 2026-03-02

---

**Initialize the package with `xtime.Init(cfg)` to atomically set a global timezone and locale, then use period helpers like `NextDailyTime` and `InTimezone` to schedule game events and convert player timestamps without lock contention.**

The `xtime` package in `go-pantheon/fabrica-util` provides a concurrent-safe abstraction layer atop Go's standard `time` library, purpose-built for game servers that must coordinate daily quest resets, weekly leaderboards, and player-local timestamp formatting across multiple timezones. Unlike raw `time` operations, `xtime` stores the server location in an `atomic.Value` for lock-free reads and provides deterministic period calculations that guarantee "next occurrence" times always fall after the current timestamp.

## Initializing the Global Timezone and Locale

All `xtime` operations depend on a one-time initialization that loads the server-wide timezone and default language. The `Init` function in [`xtime/time.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/time.go) (lines 22–54) performs three atomic setup steps:

1. **Loads the IANA location** via `time.LoadLocation` and stores it in an `atomic.Value` (lines 33–38)
2. **Parses and validates** the language code against a whitelist in [`xtime/locale.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/locale.go) (lines 15–24)
3. **Initializes locale data** from embedded JSON or falls back to English

If the package is used without initialization, `GetLocation()` safely returns `time.UTC`, ensuring nil-pointer protection.

```go
import "github.com/go-pantheon/fabrica-util/xtime"

func initServerTime() error {
    cfg := xtime.Config{
        Language: xtime.LanguageEn, // Default for system logs
        Timezone: "UTC",            // Server-wide timezone
    }
    return xtime.Init(cfg)
}

```

## Managing Timezones for Server and Player Time

The package distinguishes between the **server's configured location** (used for cron-like scheduling) and **arbitrary timezone conversion** (used for player display).

### Retrieving the Server Location

`GetLocation()` returns the `*time.Location` stored during `Init`. Because it reads from an `atomic.Value`, it is safe to call from thousands of concurrent goroutines handling player connections without mutex overhead.

```go
now := time.Now().In(xtime.GetLocation())

```

### Converting to Player Local Time

`InTimezone(t, tz)` converts any `time.Time` to a specified IANA timezone string. This is essential when displaying event times to players in regions like JST or EST while storing everything as UTC in your database.

```go
func showPlayerLocalTime(unixSeconds int64, playerTZ string) (time.Time, error) {
    utcTime := time.Unix(unixSeconds, 0)
    return xtime.InTimezone(utcTime, playerTZ)
}

```

## Period Calculations for Game Events

The [`xtime/time.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/time.go) file exports boundary and scheduling helpers that handle the complex logic of "when is the next 04:00 daily reset" or "when is the first Monday of next month."

### Calculating Period Boundaries

Use these functions to snap timestamps to the start of a period:

- `StartOfDay(t)` – Midnight of the current day
- `StartOfWeek(t)` – Monday 00:00 of the current week
- `StartOfMonth(t)` – First day of month at 00:00

### Scheduling Next Occurrences with Offsets

The `NextDailyTime`, `NextWeeklyTime`, and `NextMonthlyTime` functions accept a `time.Duration` offset to schedule events that don't start at midnight. Internally, they rewind the input by the delay, snap to the period boundary, advance one full period, then re-add the delay—guaranteeing the result is always strictly after the input time.

**Daily Quest Example (04:00 server time):**

```go
var dailyQuestDelay = 4 * time.Hour

func nextDailyQuest(now time.Time) time.Time {
    // Align to server location, then compute next 04:00 occurrence
    now = now.In(xtime.GetLocation())
    return xtime.NextDailyTime(now, dailyQuestDelay)
}

```

**Weekly Raid Example (Monday 20:00):**

```go
var raidDelay = 20 * time.Hour // 20:00 is 20 hours after midnight

func nextWeeklyRaid(now time.Time) time.Time {
    now = now.In(xtime.GetLocation())
    return xtime.NextWeeklyTime(now, raidDelay)
}

```

**Monthly Leaderboard Reset (1st day 00:00):**

```go
func nextMonthlyReset(now time.Time) time.Time {
    now = now.In(xtime.GetLocation())
    return xtime.NextMonthlyTime(now, 0) // No offset needed
}

```

## Locale-Aware Formatting

The [`xtime/format.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/format.go) and [`xtime/locale.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/locale.go) files provide human-readable output that respects the language set during `Init` or overridden per-call.

### Displaying Relative Times

`FormatRelative` (lines 40–47 of [`format.go`](https://github.com/go-pantheon/fabrica-util/blob/main/format.go)) renders strings like "5 minutes ago" or "2 hours from now" based on the current locale.

```go
func formatEventCountdown(eventTime time.Time) string {
    return xtime.FormatRelative(eventTime)
}

```

### Localized Date Templates

`FormatLocalized` uses template tokens like `{%w}` for weekday, `{%M}` for month name, and `{%d}` for day. Switch languages at runtime with `SetLocale` from [`locale.go`](https://github.com/go-pantheon/fabrica-util/blob/main/locale.go).

```go
func localizedAnnouncement(t time.Time, lang xtime.Language) string {
    // Temporarily switch locale for this player
    _ = xtime.SetLocale(lang)
    
    // Template: "Today is Monday, December 25, 2023"
    return xtime.FormatLocalized(t, "Today is {%w}, {%M} {%d}, {%y}")
}

```

## Complete Game Server Integration Example

This full example demonstrates initializing the package, calculating the next daily quest, converting to a player's Tokyo timezone, and rendering a Chinese-language timestamp:

```go
package main

import (
    "fmt"
    "time"
    
    "github.com/go-pantheon/fabrica-util/xtime"
)

func main() {
    // 1. Initialize server-wide configuration
    if err := xtime.Init(xtime.Config{
        Language: xtime.LanguageEn,
        Timezone: "UTC",
    }); err != nil {
        panic(err)
    }
    
    now := time.Now()
    
    // 2. Calculate next daily quest at 04:00 UTC
    nextQuest := xtime.NextDailyTime(now, 4*time.Hour)
    fmt.Println("Next daily quest:", nextQuest.In(xtime.GetLocation()))
    
    // 3. Convert to player local time (Tokyo)
    playerTime, _ := xtime.InTimezone(now, "Asia/Tokyo")
    fmt.Println("Player local time:", playerTime)
    
    // 4. Show relative time for an event that started 2 hours ago
    pastEvent := now.Add(-2 * time.Hour)
    fmt.Println("Event status:", xtime.FormatRelative(pastEvent))
    
    // 5. Render localized log in Chinese
    _ = xtime.SetLocale(xtime.LanguageZhCN)
    fmt.Println("中文日志:", xtime.FormatLocalized(now, "现在是{%y}年{%M}{%d}日，{%w}"))
}

```

## Summary

- **Initialize once** with `xtime.Init(cfg)` to atomically set the global timezone and language; the package safely falls back to UTC if uninitialized.
- **Use `GetLocation()`** for lock-free access to the server timezone, and **`InTimezone(t, tz)`** to convert to arbitrary player timezones.
- **Schedule recurring events** with `NextDailyTime`, `NextWeeklyTime`, and `NextMonthlyTime`, which accept duration offsets to handle non-midnight resets (e.g., 04:00 daily quests).
- **Format for players** using `FormatRelative` for durations or `FormatLocalized` with template tokens (`{%w}`, `{%M}`), switching languages via `SetLocale`.
- **Reference implementations** are found in [`xtime/time.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/time.go) (period logic), [`xtime/locale.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/locale.go) (language handling), and [`xtime/format.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/format.go) (template rendering).

## Frequently Asked Questions

### What happens if I call `xtime.Init` multiple times?

`xtime.Init` is designed to be called once at server startup. While subsequent calls will attempt to reload the location and locale, the package is optimized for a single initialization pattern. For dynamic language switching per player, use `SetLocale` rather than reinitializing the entire package.

### How does the package handle daylight saving time transitions?

Because `xtime` stores the `*time.Location` object loaded via `time.LoadLocation`, it automatically respects all IANA timezone rules including DST shifts. When you pass a `time.Time` through `InTimezone` or calculate next occurrences using `NextDailyTime`, the underlying `time` package handles the offset transitions according to the zone database.

### Can I calculate periods for a timezone different from the server default?

Yes. While `NextDailyTime` and similar functions use the server location from `GetLocation()`, you can convert any time to a different zone first using `InTimezone`, then pass that converted time into the period functions. The calculations will respect the location embedded in the `time.Time` object.

### Where is the locale data stored?

Locale JSON files are embedded in the binary and loaded into memory during `Init` or `SetLocale`. The `validLanguageCodes` map in [`xtime/locale.go`](https://github.com/go-pantheon/fabrica-util/blob/main/xtime/locale.go) validates languages against available translations, and the active locale templates are held in package-level variables for fast template lookups during `FormatLocalized` calls.