# How Zakirullin Files Handles Timezones for Journals: A Technical Deep Dive

> Discover how Zakirullin Files handles timezones for journals. Learn about per user timezone settings, UTC fallback, and consistent application across filenames and timestamps.

- Repository: [Artem Zakirullin/files.md](https://github.com/zakirullin/files.md)
- Tags: deep-dive
- Published: 2026-05-21

---

**Zakirullin Files stores a per-user timezone in [`config.json`](https://github.com/zakirullin/files.md/blob/main/config.json) and applies it consistently to journal filenames, daily headers, and entry timestamps, falling back to UTC when the setting is invalid or empty.**

Zakirullin Files is a Telegram-based journaling system that organizes entries as markdown files. To ensure accurate chronological organization across different geographic regions, the repository implements comprehensive **timezone-aware journaling** that governs file naming, header generation, and timestamp formatting based on individual user preferences stored in [`server/userconfig/userconfig.go`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go).

## Per-User Timezone Configuration

The timezone architecture centers on the `Config` struct, which provides thread-safe access to user-specific settings. Each user has a dedicated configuration file where the preferred IANA timezone identifier is persisted.

### Reading the Timezone Setting

The `Timezone()` method retrieves the stored identifier and returns a `*time.Location` instance validated against Go's standard IANA database. If the configuration field is empty or the identifier is invalid, the method safely defaults to **UTC**.

```go
// server/userconfig/userconfig.go:90-103
func (c *Config) Timezone() *time.Location {
    cfg, _ := c.read(c.filename)

    if cfg.Timezone == "" {
        return time.UTC
    }

    location, err := time.LoadLocation(cfg.Timezone)
    if err != nil {
        return time.UTC
    }
    return location
}

```

### Persisting Timezone Changes via Bot UI

Users modify their timezone through an inline keyboard interface in the Telegram bot. When a user selects a new timezone, the bot handler extracts the IANA identifier and delegates persistence to the `SetTimezone` method.

```go
// server/bot.go
func (b *Bot) setTimezone(params []string) error {
    timezone := params[0]                     // e.g. "America/New_York"
    if err := b.cfg.SetTimezone(timezone); err != nil {
        return fmt.Errorf("setTimezone : %w", err)
    }
    return b.showTimezone(nil)               // refresh the UI
}

```

The `SetTimezone` method acquires a user-specific mutex before writing to prevent race conditions during concurrent updates.

```go
// server/userconfig/userconfig.go:105-119
func (c *Config) SetTimezone(tz string) error {
    lock := c.userLock()
    lock.Lock()
    defer lock.Unlock()

    cfg, err := c.read(c.filename)
    if err != nil { … }
    cfg.Timezone = tz
    return c.write(cfg)
}

```

## Timezone-Aware Journal Operations

The [`server/journal/journal.go`](https://github.com/zakirullin/files.md/blob/main/server/journal/journal.go) package consumes the validated `*time.Location` to localize all temporal metadata. Every operation that involves dates or times first converts the instant to the user's zone using `time.Time.In(timezone)`.

### Date-Based Filename Generation

Journal files are organized by local date using the `todayJournalFilename` function. This ensures that a user in Tokyo receives a different filename than a user in London when journaling simultaneously, preventing cross-date entry mixing.

```go
// server/journal/journal.go:28-30
func todayJournalFilename(timezone *time.Location) string {
    return Now().In(timezone).Format("2006.01 January.md")
}

```

### Localized Daily Headers

When initializing a new day's journal, `todayHeader` generates a markdown heading that reflects the day, month, and weekday according to the user's local calendar.

```go
// server/journal/journal.go:32-35
func todayHeader(timezone *time.Location) string {
    nowTZ := Now().In(timezone)
    return fmt.Sprintf("## %d %s, %s",

        nowTZ.Day(), nowTZ.Format("January"), nowTZ.Weekday())
}

```

### Entry Timestamp Formatting

Both `AddRecord` and `AddEmoji` format timestamps by first converting the current instant to the user's timezone. This ensures that every journal entry displays the correct local time regardless of the server's system clock.

```go
// server/journal/journal.go:58-66
timestamp := Now().In(timezone).Format("`15:04`")
if txt.HasImage(record) { … } else {
    record = fmt.Sprintf("%s %s\n", timestamp, record)
}

```

## End-to-End Message Flow

The bot orchestrates timezone consistency through [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go) by retrieving the user's location at the entry point and propagating it through the call stack. When processing an incoming message, the bot fetches the timezone via `b.cfg.Timezone()` and passes it to both the chat rendering logic and the journal persistence layer.

```go
// server/bot.go (excerpt)
msgHash, err := b.appendToChat(msg, b.cfg.Timezone())
// …
err := journal.AddRecord(b.fs, content, b.cfg.Timezone())

```

This design ensures that the filename, daily header, and inline timestamp within a single journal entry all reference the same temporal frame of reference.

## Summary

- Zakirullin Files maintains **per-user timezone** settings in [`server/userconfig/userconfig.go`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go), safely defaulting to **UTC** when the `timezone` field is empty or invalid.
- The `Timezone()` method validates IANA identifiers using `time.LoadLocation` and returns a `*time.Location` for consistent time conversion.
- Journal filenames reflect local dates via `todayJournalFilename` in [`server/journal/journal.go`](https://github.com/zakirullin/files.md/blob/main/server/journal/journal.go), ensuring entries are filed under the correct daily document.
- All timestamps are localized using `time.Time.In(timezone)` before formatting in `AddRecord` and `AddEmoji`.
- The `SetTimezone` method provides thread-safe updates through the bot UI, persisting changes to the user's [`config.json`](https://github.com/zakirullin/files.md/blob/main/config.json) file.

## Frequently Asked Questions

### What is the default timezone in Zakirullin Files?

If the `timezone` field in [`config.json`](https://github.com/zakirullin/files.md/blob/main/config.json) is empty or contains an invalid IANA identifier, the system defaults to **UTC**. This fallback is hardcoded in the `Timezone()` method in [`server/userconfig/userconfig.go`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go) to guarantee the application always operates with a valid `*time.Location` regardless of configuration state.

### How do I change my timezone in Zakirullin Files?

Users can update their timezone through the "Timezone" button in the Telegram bot interface. This triggers the `setTimezone` handler in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go), which calls `Config.SetTimezone` to write the new IANA identifier (such as "Europe/Berlin" or "Pacific/Auckland") to the configuration file.

### Does Zakirullin Files handle daylight saving time transitions?

Yes. By utilizing standard IANA timezone identifiers (e.g., "America/New_York" rather than fixed offsets) and Go's `time.LoadLocation` function, Zakirullin Files automatically respects daylight saving time rules. The system calculates the correct offset based on the specific date and region without requiring manual clock adjustments.

### Where does Zakirullin Files store timezone settings?

Per-user timezone settings are stored in individual [`config.json`](https://github.com/zakirullin/files.md/blob/main/config.json) files managed by [`server/userconfig/userconfig.go`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go). The `Timezone` field is read on every journal operation—including filename generation, header creation, and timestamp formatting—to ensure complete consistency with the user's selected locale.