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

Zakirullin Files stores a per-user timezone in 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.

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.

// 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.

// 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.

// 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 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.

// 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.

// 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.

// 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 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.

// 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, 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, 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 file.

Frequently Asked Questions

What is the default timezone in Zakirullin Files?

If the timezone field in 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 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, 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 files managed by 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →