Skip to content

Parsing

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

Parsing

Four entry points, one for each combination of layout language and default location:

ptime.Parse(layout, value)                          // pattern letters, UTC default
ptime.ParseInLocation(layout, value, loc)           // pattern letters, explicit default
ptime.ParseTimeFormat(layout, value)                // reference time, UTC default
ptime.ParseTimeFormatInLocation(layout, value, loc) // reference time, explicit default

The layout languages are the ones described in Formatting, so anything Format writes, Parse reads.

Basics

pt, err := ptime.Parse(ptime.DateTime, "1394-07-02 12:59:59")
pt, err = ptime.Parse(ptime.RFC3339, "1394-07-02T12:59:59+03:30")
pt, err = ptime.Parse("d MMM yyyy", "2 مهر 1394")
pt, err = ptime.Parse("d MMI yyyy", "2 میزان 1394")
pt, err = ptime.ParseTimeFormat("2006/01/02", "1394/07/02")

Time zones

Following time.Parse, a value without zone information is interpreted in UTC:

pt, _ := ptime.Parse(ptime.DateOnly, "1404-05-20")
pt.Location() // UTC

Use ParseInLocation when the text comes from a context with a known zone:

pt, _ := ptime.ParseInLocation(ptime.DateOnly, "1404-05-20", ptime.Iran())
pt.Location() // Asia/Tehran

A zone in the value always wins over the default:

Value contains Result
Z UTC
+03:30, +0330, +03 a fixed zone at that offset
Asia/Tehran via the z or MST pattern that named location
nothing the default location

If the z pattern matches a name the host has no data for, parsing fails with unknown time zone — unless the value also carried an offset, in which case a fixed zone with that name and offset is used.

Field defaults

Fields absent from the layout default to Farvardin 1 of year 1, 00:00:00:

pt, _ := ptime.Parse(ptime.TimeOnly, "12:59:59")
pt.Date() // 1 Farvardin 1

Two digit years

yy and 06 map onto the 1350-1449 window, which covers every year the calendar is realistically used for:

Input Year
00 – 49 1400 – 1449
50 – 99 1350 – 1399
ptime.Parse("yy/MM/dd", "04/05/20") // 1404-05-20
ptime.Parse("yy/MM/dd", "94/07/02") // 1394-07-02

Digit counts

Two character patterns require exactly two digits; single character patterns accept one or two. This matches time.Parse, where 01 needs two digits and 1 accepts one or two.

ptime.Parse("yyyy-MM-dd", "1404-5-20")  // error: MM needs two digits
ptime.Parse("yyyy-M-d", "1404-5-20")    // fine
ptime.Parse("yyyy-M-d", "1404-05-20")   // also fine

Because of this leniency, Parse(layout, Format(layout)) is not always byte identical to the input — but it always yields the same instant.

Careful with adjacent numeric fields. A layout like yyyyMMdd with no separators is ambiguous for a greedy parser, the same way it is in the standard library. Keep a separator between numeric fields when the text will be parsed again.

Validation

Out of range values are errors, not silently clamped:

ptime.Parse(ptime.DateOnly, "1394-13-02") // error: month out of range
ptime.Parse(ptime.DateOnly, "1394-07-31") // error: Mehr has 30 days
ptime.Parse(ptime.DateOnly, "1394-12-30") // error: 1394 is not a leap year
ptime.Parse(ptime.DateOnly, "1403-12-30") // fine: 1403 is a leap year
ptime.Parse(ptime.DateOnly, "1394-07-02 extra") // error: extra text

Errors

Failures return a *ParseError that names the layout element that did not match and the input that remained:

_, err := ptime.Parse(ptime.DateOnly, "1394/07/02")

var perr *ptime.ParseError
if errors.As(err, &perr) {
    perr.Layout     // yyyy-MM-dd
    perr.Value      // 1394/07/02
    perr.LayoutElem // -
    perr.ValueElem  // /07/02
    perr.Message    // "" for a plain mismatch
}

fmt.Println(err)
// ptime: cannot parse "/07/02" as "-" in "1394/07/02"

Robustness

The parsers are covered by three fuzz targets:

  • FuzzParse — a successful parse is stable under Format and Parse again
  • FuzzParseNeverPanics — arbitrary layouts and values never panic
  • FuzzRoundTrip — every representable moment survives a Format/Parse round trip

Run them locally with make fuzz, or make fuzz FUZZTIME=5m for a longer pass.

Clone this wiki locally