Skip to content

Timestamp Converter — Go source

Convert Unix epoch timestamps to human-readable dates and back. See seconds, milliseconds, ISO 8601, UTC, local and relative time at once.

This is the Go implementation — the same logic the interactive tool runs, in a shareable, citable form.

// Unix epoch (seconds & milliseconds) <-> human forms: ISO 8601, UTC
// string, local date string, and relative time ("3 hr ago" / "in 2 day").
//
// Language: Go
// CosmoDev polyglot showcase port of the `timestamp` tool.
// Ported from src/tools/TimestampConverter.tsx — display source, part of
// CosmoDev's polyglot tool pages.
//
// Every form derives from a single epoch-seconds value via the standard
// time package. relativeTime() buckets |now - t| into sec/min/hr/day/
// month/yr using the same 60s / 60m / 24h / 30d / 365d thresholds and
// signs the phrase. JS toUTCString emits "GMT" for the zone; Go's
// RFC1123Z yields "+0000", which we swap to "GMT" for byte-parity.
package main

import (
	"fmt"
	"strconv"
	"strings"
	"time"
)

const invalid = "—"

// relParts is the (count, unit) pair every language computes identically.
type relParts struct {
	n    int64
	unit string
}

// relativeBuckets maps an absolute millisecond distance to its bucket.
// Exported as the headline pure transform so tests can pin each edge.
func relativeBuckets(absMs int64) relParts {
	const (
		minMs  = 60_000
		hrMs   = 3_600_000
		dayMs  = 86_400_000
		monMs  = dayMs * 30
		yearMs = dayMs * 365
	)
	switch {
	case absMs < minMs:
		return relParts{(absMs + 500) / 1000, "sec"}
	case absMs < hrMs:
		return relParts{(absMs + minMs/2) / minMs, "min"}
	case absMs < dayMs:
		return relParts{(absMs + hrMs/2) / hrMs, "hr"}
	case absMs < monMs:
		return relParts{(absMs + dayMs/2) / dayMs, "day"}
	case absMs < yearMs:
		return relParts{(absMs + monMs/2) / monMs, "month"}
	default:
		return relParts{(absMs + yearMs/2) / yearMs, "yr"}
	}
}

// RelativeTime returns "3 hr ago" / "in 2 day" etc., mirroring the island.
// nowMs is a parameter so the result is deterministic and testable.
func RelativeTime(ms, nowMs int64) string {
	diff := ms - nowMs
	abs := diff
	if abs < 0 {
		abs = -abs
	}
	p := relativeBuckets(abs)
	s := fmt.Sprintf("%d %s", p.n, p.unit)
	if diff < 0 {
		return s + " ago"
	}
	return "in " + s
}

// FormatTimestamp renders every form the tool exposes from epoch seconds.
// Invalid instants render an em dash, matching the live UI.
func FormatTimestamp(secs int64, nowMs int64) map[string]string {
	t := time.Unix(secs, 0)
	utc := t.UTC()
	// "Z07:00" prints "Z" for UTC; ".000" matches Date.toISOString ms.
	iso := utc.Format("2006-01-02T15:04:05.000Z07:00")
	// RFC1123Z gives "Wed, 14 Jun 2017 07:00:00 +0000"; JS gives "… GMT".
	utcStr := strings.Replace(utc.Format(time.RFC1123Z), "+0000", "GMT", 1)
	// toLocaleString is locale-dependent; this is the local-zone form.
	local := t.Format("2006-01-02 15:04:05 MST")
	return map[string]string{
		"Epoch (s)":  strconv.FormatInt(secs, 10),
		"Epoch (ms)": strconv.FormatInt(secs*1000, 10),
		"ISO 8601":   iso,
		"UTC":        utcStr,
		"Local":      local,
		"Relative":   RelativeTime(secs*1000, nowMs),
	}
}

// FromISO parses an ISO 8601 / datetime-local ("YYYY-MM-DDTHH:mm") string
// into epoch seconds in the host's local zone — the same path the island's
// datetime-local input takes. Returns ok=false for unparseable input.
func FromISO(iso string) (secs int64, ok bool) {
	layouts := []string{
		"2006-01-02T15:04:05",
		"2006-01-02T15:04",
		time.RFC3339,
		"2006-01-02 15:04:05",
	}
	for _, l := range layouts {
		if t, err := time.ParseInLocation(l, iso, time.Local); err == nil {
			return t.Unix(), true
		}
	}
	return 0, false
}

func main() {
	now := time.Now().UnixMilli()
	for k, v := range FormatTimestamp(time.Now().Unix(), now) {
		fmt.Printf("%-10s %s\n", k+":", v)
	}
}

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →