Skip to content
Amir Iranmanesh edited this page Aug 11, 2026 · 1 revision

FAQ

Is ptime.Time safe to use from multiple goroutines?

Yes, as long as you do not call a pointer method on a shared value. Time is an immutable value; every method with a value receiver returns a new one. The pointer methods (Set, SetHour, At, Scan, UnmarshalJSON, …) mutate, so they need the usual care.

Iran() and Afghanistan() are safe to call concurrently — the lookup is guarded by sync.OnceValue.


Which leap year rule is used?

The arithmetic 33 year cycle rule: 8 leap years in every 33, determined by (25*year + 11) mod 33 < 8.

This is the rule almost every software implementation uses. The official Iranian calendar is astronomical, defined by the March equinox at the 52.5°E meridian, and the two rules agree for the range this package targets. If you need equinox-exact behavior for years far outside it, this is not the package for you.


Why is Gregorian year 1097 the lower bound?

Below it the conversion formulas leave their valid range and start producing nonsense. Rather than return wrong dates, New returns the zero Time:

old := time.Date(1000, time.January, 1, 0, 0, 0, 0, time.UTC)
ptime.New(old).IsZero() // true

Dates between 1097 and the 1582 Gregorian reform are handled through the Julian calendar, which is what those dates actually meant at the time.


Why does Parse default to UTC instead of Tehran?

Because time.Parse defaults to UTC, and surprising a Go developer is worse than the extra keystrokes. Use ParseInLocation when you know the zone:

pt, err := ptime.ParseInLocation(ptime.DateOnly, "1404-05-20", ptime.Iran())

Why does Value() store a Gregorian timestamp?

So the column stays an ordinary SQL timestamp. ORDER BY, BETWEEN, indexes, date_trunc and every other database date function keep working, and other consumers of the database can read it without knowing about the Persian calendar. Conversion happens at the edge of your program, which is where it belongs. See Encoding.


Why is Format a different layout language from TimeFormat?

Historical reasons, and both turned out to be useful. Format uses pattern letters (yyyy/MM/dd), which is what most non-Go date libraries use and what most designers and product people write in a ticket. TimeFormat uses the Go reference time (2006/01/02), so layouts you already have for time.Time work unchanged. Both languages have a matching parser.


.ns and .000000000 give different output. Why?

ns writes the nanosecond field as a plain decimal number, without padding — 5 nanoseconds is 5, not 000000005. It is a numeric field, not a fraction. The .000/.999 patterns are proper fractions and are what you want in anything that will be parsed again:

pt.Format("ns")          // 5
pt.Format(".000000000")  // .000000005

Can I use this as a JSON map key, or with YAML?

Yes. Time implements encoding.TextMarshaler and encoding.TextUnmarshaler, which is what encoding/json uses for map keys and what most YAML, TOML and form-binding libraries look for.


How fast is it?

Formatting is a single pass into a byte buffer, so Format costs one allocation and AppendFormat costs none. Conversion is pure integer arithmetic — no floating point, no allocation. Location lookups are cached.

Run make bench to see the numbers on your machine.


Does it need the time zone database at runtime?

Only if you use Iran(), Afghanistan(), or a named zone. Both helpers fall back to a fixed offset zone when time.LoadLocation fails, so a scratch container without tzdata still gets correct offsets — it just loses daylight saving history, which Iran no longer observes anyway.

To be certain, import the embedded database:

import _ "time/tzdata"

Is there a calendar widget, holiday list, or number localization?

No. This package converts and formats dates. Persian digits, holidays and UI components are separate concerns, and keeping them out is why the package has no dependencies. Converting digits is four lines:

var persianDigits = strings.NewReplacer(
    "0", "۰", "1", "۱", "2", "۲", "3", "۳", "4", "۴",
    "5", "۵", "6", "۶", "7", "۷", "8", "۸", "9", "۹",
)

persianDigits.Replace(pt.Format(ptime.DateOnly)) // ۱۴۰۴-۰۵-۲۰

I found a wrong date. What do you need?

Open an issue with the Persian and Gregorian dates involved, the layout string if formatting or parsing is affected, and what you expected instead. A failing test case is the fastest path to a fix.