isoparse

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 19, 2026 License: Apache-2.0 Imports: 5 Imported by: 0

README

isoparse

CI Go Reference Go Report Card Coverage License

Package isoparse parses ISO 8601 date, time, and datetime strings into Go time.Time values — without requiring the caller to specify a layout in advance.

It is a Go port of the isoparser module (by Paul Ganssle) from Python's dateutil library, adapted to Go's time package semantics.

Install

Requires Go 1.25 or newer.

go get github.com/bsolomon1124/isoparse

Usage

package main

import (
	"fmt"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	t, err := isoparse.ParseDatetime("2024-03-15T14:30:00Z")
	if err != nil {
		panic(err)
	}
	fmt.Println(t) // 2024-03-15 14:30:00 +0000 UTC

	d, _ := isoparse.ParseDate("2024-W11-5")
	fmt.Println(d) // 2024-03-15 00:00:00 ...

	hms, loc, _ := isoparse.ParseTime("14:30:00+02:00")
	fmt.Println(hms, loc) // [14 30 0 0] UTC
}

Full API documentation is on pkg.go.dev.

Supported formats

Dates:

Format Example
YYYY 2024
YYYY-MM 2024-03
YYYY-MM-DD 2024-03-15
YYYYMMDD 20240315
YYYY-DDD 2024-075 (ordinal)
YYYYDDD 2024075 (ordinal)
YYYY-Www-D 2024-W11-5 (ISO week)
YYYYWwwD 2024W115 (ISO week)

Times (with optional Z, ±HH, ±HHMM, or ±HH:MM offset):

Format Example
HH 14
HH:MM / HHMM 14:30
HH:MM:SS 14:30:00
HH:MM:SS.ffffff 14:30:00.123456

Datetimes are any date + any time joined by a non-numeric ASCII separator (T is standard; space, _, -, etc. are also accepted).

API

func ParseDatetime(s string) (time.Time, error)
func ParseDate(s string) (time.Time, error)
func ParseTime(s string) (components [4]int, tz *time.Location, err error)
func SetLoc(t time.Time, loc *time.Location) time.Time

type ParseError struct {
    Datetime string
    Message  string
}

ParseDatetime accepts date-only input as well, but ParseDate is faster when the caller knows no time component is present. Parse failures return a *ParseError — check via errors.As.

Time zone handling

Go's time.Time has no concept of a naive datetime; a Location is mandatory. This package follows these rules:

  • No offset in the input → result uses time.Local.
  • Offset present (Z, ±HH, ±HHMM, ±HH:MM) → result uses time.FixedZone("UTC", secondsEast). No IANA name is inferred; e.g. -05:00 is inherently ambiguous between EST, ECT, COT, etc.
  • Unicode minus sign (U+2212) is accepted in place of ASCII -.

SetLoc(t, loc) returns a new time.Time with the same wall-clock components but a different Location — unlike time.Time.In, which preserves the instant and shifts the wall clock.

Conformance to ISO 8601

Conforms mostly to ISO 8601:2004. Known deviations:

  • Separator between date and time may be any non-numeric ASCII character, not strictly T.
  • Years are restricted to 00019999 (no expanded representations, no negative years).
  • Time intervals and recurring intervals (§4.4, §4.5) are not parsed.
  • Reduced-accuracy century/hour representations ("19" for 1900, "23" for 23:00:00) are not accepted.
  • Fractional minutes/hours (e.g. 14:30,5) are not parsed; only fractional seconds are supported.
  • Second-fraction precision beyond 9 digits is truncated (Go's time has nanosecond precision).

Testing

go test -race -cover ./...

Current statement coverage: 99.5%. The single uncovered line is a defensive branch in ParseDatetime that is unreachable under the current control flow.

CI runs on Go 1.25 and 1.26 across Linux, macOS, and Windows, plus a golangci-lint lint job on every push and PR.

License

Apache License 2.0.

Documentation

Overview

Package isoparse parses ISO 8601 date, time, and datetime strings into time.Time values without requiring a format string.

The three entry points are ParseDatetime, ParseDate, and ParseTime. Parse failures return a *ParseError; inspect via errors.As.

Inputs that omit a UTC offset are returned in time.Local; inputs with a recognizable offset use time.FixedZone (no IANA name is inferred, since an offset like -05:00 is DST-ambiguous). See SetLoc for re-attaching a different *time.Location without shifting wall-clock components.

Most of the parsing logic is ported from the isoparser module (by Paul Ganssle) in Python's dateutil library; semantics are adapted to Go's time package. See the project README for supported formats and deviations from the ISO 8601:2004 standard.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ParseDate

func ParseDate(dateString string) (time.Time, error)

ParseDate parses an ISO 8601 date string (no time component) into a time.Time with time.Local as its location.

Example
package main

import (
	"fmt"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	t, _ := isoparse.ParseDate("2024-03-15")
	fmt.Println(t.Format("2006-01-02"))
}
Output:
2024-03-15
Example (Ordinal)
package main

import (
	"fmt"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	// Ordinal date: the 75th day of 2024.
	t, _ := isoparse.ParseDate("2024-075")
	fmt.Println(t.Format("2006-01-02"))
}
Output:
2024-03-15
Example (WeekDate)
package main

import (
	"fmt"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	// ISO week date: week 11, day 5 of 2024.
	t, _ := isoparse.ParseDate("2024-W11-5")
	fmt.Println(t.Format("2006-01-02"))
}
Output:
2024-03-15

func ParseDatetime

func ParseDatetime(datetime string) (time.Time, error)

ParseDatetime parses an ISO 8601 datetime (combined date and time). The date/time separator may be any non-numeric ASCII character, not strictly "T".

ParseDatetime also accepts date-only input, but ParseDate is faster when the caller knows no time portion is present.

Behaves like time.Parse but without a layout argument: on error, the returned time.Time is the zero value.

Example
package main

import (
	"fmt"
	"time"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	t, err := isoparse.ParseDatetime("2024-03-15T14:30:45Z")
	if err != nil {
		panic(err)
	}
	fmt.Println(t.Format(time.RFC3339Nano))
}
Output:
2024-03-15T14:30:45Z
Example (Basic)
package main

import (
	"fmt"
	"time"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	// Compact (no-separator) representation.
	t, _ := isoparse.ParseDatetime("20240315T143045Z")
	fmt.Println(t.Format(time.RFC3339))
}
Output:
2024-03-15T14:30:45Z
Example (Offset)
package main

import (
	"fmt"
	"time"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	// Non-zero offset → time.FixedZone.
	t, _ := isoparse.ParseDatetime("2024-03-15T10:00:00-05:00")
	fmt.Println(t.Format(time.RFC3339))
}
Output:
2024-03-15T10:00:00-05:00

func ParseTime

func ParseTime(timeString string) (components [4]int, tz *time.Location, err error)

ParseTime parses an ISO 8601 time string (no date component) and returns the component ints [hour, minute, second, nanosecond] along with the location. Accepted forms include HH, HH:MM, HHMM, HH:MM:SS, HHMMSS, and any of the above with a fractional seconds suffix and/or timezone suffix.

Example
package main

import (
	"fmt"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	components, _, _ := isoparse.ParseTime("14:30:45.5Z")
	fmt.Printf("h=%d m=%d s=%d ns=%d\n", components[0], components[1], components[2], components[3])
}
Output:
h=14 m=30 s=45 ns=500000000

func SetLoc

func SetLoc(t time.Time, loc *time.Location) time.Time

SetLoc returns a new time.Time with the same wall-clock components (year, month, day, hour, minute, second, nanosecond) as t, but with loc as its *time.Location.

Unlike time.Time.In, SetLoc does not preserve the instant: it reinterprets the same wall-clock reading in loc. Use this when an input like "2024-03-15T10:00:00" was meant to be in, say, America/New_York rather than the machine's time.Local.

Example
package main

import (
	"fmt"
	"time"

	"github.com/bsolomon1124/isoparse"
)

func main() {
	// Parsing "2024-03-15T10:00:00" yields a Time in time.Local. If the input
	// was actually meant to be in a specific zone, SetLoc re-attaches that
	// zone without shifting the wall-clock components (unlike Time.In).
	t, _ := isoparse.ParseDatetime("2024-03-15T10:00:00")
	ny, _ := time.LoadLocation("America/New_York")
	t = isoparse.SetLoc(t, ny)
	fmt.Println(t.Format(time.RFC3339))
}
Output:
2024-03-15T10:00:00-04:00

Types

type ParseError

type ParseError struct {
	Datetime string // the offending input
	Message  string // optional; a specific reason
}

ParseError describes a failure to parse a date, time, or datetime string. It is the sole error type returned by the Parse* functions in this package; use errors.As to inspect.

func (*ParseError) Error

func (e *ParseError) Error() string

Error implements the [error] interface.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL