A Go implementation of RFC 9557: Internet Extended Date/Time Format (IXDTF).
IXDTF extends RFC 3339 by adding optional suffix elements for timezone names and additional metadata while maintaining full backward compatibility.
strictMode := false
rfc9557 := "2025-02-03T04:05:06+09:00[Asia/Tokyo][!u-ca=gregory]"
parsedTime, ixdtfExtensions, _ := ixdtf.Parse(rfc9557, strictMode)
// parsedTime => 2025-02-03 04:05:06 +0900 JST
// ixdtfExtensions => &ixdtf.IXDTFExtensions{Asia/Tokyo map[u-ca:gregory] map[u-ca:true]}
result, _ := ixdtf.Format(parsedTime, ixdtfExtensions)
// result is the same rfc9557- RFC 3339 Compatible: Full backward compatibility with existing RFC 3339 date/time strings
- Extended Format Support: Handles timezone names and additional metadata via suffix elements
- Zero Dependencies: Pure Go implementation using only the standard library
- Comprehensive Validation: ABNF-based validation ensuring format compliance
go get github.com/8beeeaaat/ixdtfpackage main
import (
"fmt"
"time"
"github.com/8beeeaaat/ixdtf"
)
func main() {
rfc9557InNY := "2025-01-02T03:04:05-05:00[America/New_York][!u-ca=gregorian]"
// Parse an IXDTF string in strict mode
parsedTime, parsedExt, err := ixdtf.Parse(rfc9557InNY, true)
if err != nil {
panic(err)
}
fmt.Printf("Parsed Time: %v\n", parsedTime)
// => Parsed Time: 2025-01-02 03:04:05 -0500 EST
fmt.Printf("Extensions: %+v\n", parsedExt)
// => &ixdtf.IXDTFExtensions{Location:America/New_York Tags:map[u-ca:gregorian] Critical:map[u-ca:true]}
// Format a time with same extensions
now := time.Now()
fmt.Printf("Now: %v\n", now)
// => Now: 2025-09-01 12:34:56.123456789 +0900 JST
formattedNano, err := ixdtf.FormatNano(now, parsedExt)
if err != nil {
panic(err)
}
fmt.Printf("FormattedNano: %s\n", formattedNano)
// => FormattedNano: 2025-09-01T12:34:56.123456789+09:00[America/New_York][!u-ca=gregorian]
// Parse back the formattedNano string
// In non-strict mode, this should succeed even if there's an offset mismatch
reParsedTime, _, err := ixdtf.Parse(formattedNano, false)
if err != nil {
panic(err)
}
fmt.Printf("Re:Parsed Time: %v\n", reParsedTime)
// => Re:Parsed Time: 2025-09-01 12:34:56.123456789 +0900 JST
// In strict mode, this should error if the offset does not match the timezone
_, _, err = ixdtf.Parse(formattedNano, true)
if err != nil {
panic(err)
// => panic: IXDTFE parsing time "2025-09-01T12:34:56.123456789+09:00[America/New_York][!u-ca=gregorian]" as "2006-01-02T15:04:05.999999999Z07:00*([time-zone-name][tags])": timezone offset does not match the specified timezone
}
}strictMode := true
err = ixdtf.Validate("2023-08-07T14:30:00+09:00[America/New_York]", strictMode)
if err != nil {
fmt.Printf("Invalid format: %v\n", err)
}
// => Invalid format: IXDTFE parsing time "2023-08-07T14:30:00+09:00[America/New_York]" as "2006-01-02T15:04:05.999999999Z07:00*([time-zone-name][tags])": timezone offset does not match the specified timezoneIXDTF extends RFC 3339 with optional suffix elements:
<RFC3339-date-time>[<timezone-name>][<extension>...]
2023-08-07T14:30:00Z- Standard RFC 33392023-08-07T14:30:00Z[America/New_York]- With timezone name2023-08-07T14:30:00Z[u-ca=gregorian]- With Unicode calendar extension2025-02-03T04:05:06+09:00[Asia/Tokyo][!u-ca=gregory]- Multiple suffixes2023-08-07T14:30:00Z[!u-ca=gregorian]- Critical extension (must be processed)
IXDTF supports extension tags with specific validation rules following RFC 9557:
Extension tags undergo multi-layer validation:
- ABNF Syntax Validation: Keys and values must conform to RFC-defined patterns
- Extension Type Validation:
- Private extensions (
x-*,X-*): Rejected per BCP 178 - Experimental extensions (
_*): Rejected unless specifically configured
- Private extensions (
- Critical Extension Processing: Extensions marked with
!must be processable or rejected
ref: https://www.rfc-editor.org/rfc/rfc9557.html#section-3.2
The following extension patterns are automatically rejected:
// Private extensions
"2023-08-07T14:30:00Z[x-custom=value]" // Error: private extension cannot be processed
// Experimental extensions
"2023-08-07T14:30:00Z[_experimental=value]" // Error: experimental extension cannot be processed
// Critical private/experimental extensions also
"2023-08-07T14:30:00Z[!x-custom=value]" // Error: private extension cannot be processed
"2023-08-07T14:30:00Z[!_experimental=value]" // Error: experimental extension cannot be processedParse(s string, strict bool) (time.Time, *IXDTFExtensions, error)- Parse IXDTF stringFormat(t time.Time, ext *IXDTFExtensions) (string, error)- Format time with extensionsFormatNano(t time.Time, ext *IXDTFExtensions) (string, error)- Format with nanosecond precisionValidate(s string, strict bool) error- Validate format without parsing
The second argument strict in Parse / Validate controls how strictly the library enforces consistency between:
- the UTC offset embedded in the RFC 3339 portion, and
- the IANA time zone name supplied in a suffix.
| Mode | Behavior | Example |
|---|---|---|
true |
If the zone-derived offset for that instant differs from the RFC 3339 numeric offset, an error (ErrTimezoneOffsetMismatch) is returned. | 2025-01-01T12:00:00+09:00[America/New_York] → New York at that instant is -05:00, so mismatch → error |
false |
Mismatches do NOT produce an error. The original timestamp (its instant + numeric offset) is kept; the location is only applied if offsets match. | Same example above: no error; the provided time value is kept as-is (location not applied) |
Notes:
- An invalid or unresolvable time zone name always yields an error (regardless of mode).
- Time zones with
Etc/GMT±Xnaming are skipped for consistency checking (POSIX inverted offset semantics would cause false positives). Validatefollows the same policy: withstrict=falsean offset mismatch is considered acceptable.- Extension tag syntax and critical tag handling are independent of
strict. - Recommended usage: accept loosely formed inputs with
strict=falseat system boundaries (ingest phase), then re-normalize if needed; enforcestrict=truewhere data integrity or audit requirements apply.
IXDTFExtensions- Container for extension dataParseError- Detailed parsing error with position informationTimezoneConsistencyResult- Timezone validation results
The library provides structured error types for different failure scenarios:
_, _, err := ixdtf.Parse("invalid-date", false)
if err != nil {
if parseErr, ok := err.(*ixdtf.ParseError); ok {
fmt.Printf("Parse error, %s: %s\n", parseErr.Layout, parseErr.Value)
// => Parse error, 2006-01-02T15:04:05Z07:00: invalid-date
}
}- Go 1.24 or later
# Run tests
go test ./...
# Run tests with race detection
go test -race ./...
# Generate coverage report
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
# Lint code (requires golangci-lint)
golangci-lint run- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Make your changes and add tests
- Ensure all tests pass and code is properly formatted
- Commit your changes (
git commit -am 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This project maintains high code quality standards:
- All tests must pass
- Code coverage should not decrease
- golangci-lint must pass with zero warnings
- gosec security scan must pass
- Code must be properly formatted with
go fmt
This project is licensed under the MIT License - see the LICENSE file for details.