kronos

package module
v0.0.0-...-3ed770b Latest Latest
Warning

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

Go to latest
Published: Nov 8, 2025 License: Unlicense Imports: 11 Imported by: 0

README

Kronos Gopher

Kronos

CI Go Reference Go Report Card Go Version

Overview

Kronos is a Go package for extracting structured datetimes from natural language. It is heavily inspired by the excellent chrono JavaScript library. Using Kronos, you can turn text like "see you Thursday at 5:30pm", "4/5/2023 around 6", or "Thu, November 6th, 2025, 8:00 AM EST" in to Golang structures. Kronos will keep track of ambiguity (such as am/pm in the "around 6" example above). Features include the following.

  • Natural Language Parsing: Understands expressions like "tomorrow at 3pm", "next Friday", "in 2 weeks"
  • Flexible Date Formats: Supports ISO dates, slash dates, month names, and more
  • Relative Dates: Handles "yesterday", "last week", "3 days ago", etc.
  • Date Ranges: Parses expressions like "from Monday to Friday"
  • Time Expressions: Recognizes times like "3pm", "14:30", "noon", "midnight"
  • Timezone Support: Parses timezone abbreviations and offsets
  • Configurable: Control date order (MDY vs DMY), prefer past/future dates, strict/casual parsing
  • Multiple Locales: Built-in support for US English and British English
  • Component Inspection: Distinguish between explicitly mentioned vs. implied date parts
  • Builder Pattern API: Fluent, type-safe configuration
  • Extensive test coverage: test casese stolen from other date parsing libraries

Installation

go get github.com/kljensen/kronos

Quick Start

Basic Usage

The simplest way to parse a date is using the en package convenience functions:

package main

import (
    "fmt"
    "github.com/kljensen/kronos/en"
)

func main() {
    // Parse a single date
    date, err := en.ParseDateSimple("tomorrow at 3pm")
    if err != nil {
        panic(err)
    }
    fmt.Printf("Date: %v\n", date)

    // Parse all dates in text
    results, err := en.ParseSimple("Meet me tomorrow at 3pm or next Friday at noon")
    if err != nil {
        panic(err)
    }
    for _, r := range results {
        fmt.Printf("Found '%s' at position %d: %v\n", r.Text(), r.Index(), r.Date())
    }
}
Builder Pattern API

For more control, use the builder pattern:

package main

import (
    "fmt"
    "time"
    "github.com/kljensen/kronos"
    "github.com/kljensen/kronos/en"
)

func main() {
    // Create a parser with custom configuration
    parser := en.New().
        WithReferenceDate(time.Date(2024, 3, 15, 12, 0, 0, 0, time.UTC)).
        PreferPast().
        DateOrder(kronos.DateOrderDMY)

    results, err := parser.Parse("last Monday")
    if err != nil {
        panic(err)
    }

    for _, r := range results {
        fmt.Printf("Text: %s\n", r.Text())
        fmt.Printf("Date: %v\n", r.Date())

        // Inspect components
        comp := r.Start()
        if comp.IsCertain(kronos.ComponentDay) {
            day := comp.Get(kronos.ComponentDay)
            fmt.Printf("Day was explicitly mentioned: %d\n", *day)
        }
    }
}
Working with Components

Kronos lets you inspect which date/time parts were explicitly mentioned vs. implied:

results, _ := en.ParseSimple("tomorrow at 3pm")
comp := results[0].Start()

// Check what was explicitly mentioned
if comp.IsCertain(kronos.ComponentHour) {
    hour := comp.Get(kronos.ComponentHour)
    fmt.Printf("Hour was mentioned: %d\n", *hour)  // 15
}

// Year is implied from reference date, not mentioned in "tomorrow"
if !comp.IsCertain(kronos.ComponentYear) {
    year := comp.Get(kronos.ComponentYear)
    fmt.Printf("Year was implied: %d\n", *year)
}
Date Ranges

Kronos can parse date ranges and provides access to both start and end dates:

results, _ := en.ParseSimple("from Monday to Friday")
if results[0].End() != nil {
    start := results[0].Start().Date()
    end := results[0].End().Date()
    fmt.Printf("Range: %v to %v\n", start, end)
}
Time Ranges

Kronos can also parse time ranges within date expressions:

// Time range on a specific date
results, _ := en.ParseSimple("Wed, Nov 5, 2025 8:30 PM - 10:45 PM")
if results[0].End() != nil {
    start := results[0].Start().Date()  // 2025-11-05 20:30:00
    end := results[0].End().Date()      // 2025-11-05 22:45:00
    fmt.Printf("Event from %s to %s\n",
        start.Format("3:04 PM"),
        end.Format("3:04 PM"))
}

API Overview

Core Types
  • ParserBuilder: The main entry point for parsing. Create with en.New() or kronos.New(chrono).
  • Result: Represents a parsed date/time expression. Access via Text(), Index(), Date(), Start(), End().
  • Components: Represents individual date/time parts. Methods: Get(), IsCertain(), Date().
  • Component: Constants for date/time components (ComponentYear, ComponentMonth, ComponentDay, etc.).
Builder Methods

Configure parsing behavior by chaining builder methods:

parser := en.New().
    WithReferenceDate(refDate).  // Set reference date for relative dates
    Strict().                     // Enable strict parsing mode
    Casual().                     // Enable casual parsing mode (default)
    DateOrder(order).            // Set date component order (MDY, DMY, YMD)
    PreferPast().                // Prefer past dates when ambiguous
    PreferFuture().              // Prefer future dates when ambiguous
    PreferCurrentPeriod().       // Prefer current period (default)
    Timezone(tz)                 // Set default timezone
Parsing Methods

Execute the parser:

  • Parse(text string) ([]Result, error): Parse all date/time expressions in text
  • ParseDate(text string) (*time.Time, error): Parse and return the first date only
Package-Level Convenience Functions

The en package provides quick access for English parsing:

  • en.ParseSimple(text string) ([]Result, error): Parse with default settings
  • en.ParseDateSimple(text string) (*time.Time, error): Parse single date with defaults
  • en.New(): Create a new parser builder with casual English
  • en.StrictParser(): Create a parser builder with strict English
  • en.GBParser(): Create a parser builder with British English (DMY order)
Pre-built Configurations

The en package provides ready-to-use configurations:

  • en.Casual: Casual English parsing (recognizes informal expressions)
  • en.Strict: Strict English parsing (formal patterns only)
  • en.GB: British English parsing (DMY date order)

Examples

Relative Dates
results, _ := en.ParseSimple("yesterday")
results, _ := en.ParseSimple("tomorrow")
results, _ := en.ParseSimple("last Monday")
results, _ := en.ParseSimple("next week")
results, _ := en.ParseSimple("3 days ago")
results, _ := en.ParseSimple("in 2 weeks")
results, _ := en.ParseSimple("2 hours from now")
Absolute Dates
results, _ := en.ParseSimple("March 15, 2024")
results, _ := en.ParseSimple("3/15/2024")
results, _ := en.ParseSimple("2024-03-15")
results, _ := en.ParseSimple("15th of March")
results, _ := en.ParseSimple("March 2024")
Time Expressions
results, _ := en.ParseSimple("3pm")
results, _ := en.ParseSimple("15:30")
results, _ := en.ParseSimple("noon")
results, _ := en.ParseSimple("midnight")
results, _ := en.ParseSimple("3:30:45 PM")
results, _ := en.ParseSimple("14:30 EST")
Combined Date and Time
results, _ := en.ParseSimple("tomorrow at 3pm")
results, _ := en.ParseSimple("March 15 at 14:30")
results, _ := en.ParseSimple("next Friday at noon")
results, _ := en.ParseSimple("2024-03-15 15:30:00")
Date Order Configuration
// US format (Month/Day/Year)
parser := en.New().DateOrder(kronos.DateOrderMDY)
results, _ := parser.Parse("3/15/2024")  // March 15, 2024

// European format (Day/Month/Year)
parser = en.New().DateOrder(kronos.DateOrderDMY)
results, _ = parser.Parse("15/3/2024")   // March 15, 2024

// Or use the GB parser
results, _ = en.GBParser().Parse("15/3/2024")
Prefer Past/Future
// When parsing "March" in November, prefer last March
parser := en.New().
    WithReferenceDate(time.Date(2024, 11, 15, 12, 0, 0, 0, time.UTC)).
    PreferPast()
results, _ := parser.Parse("March")  // March 2024 (past)

// Prefer next March
parser = en.New().
    WithReferenceDate(time.Date(2024, 11, 15, 12, 0, 0, 0, time.UTC)).
    PreferFuture()
results, _ = parser.Parse("March")   // March 2025 (future)
Multiple Dates in Text
text := "The meeting is on March 15 at 2pm, with a follow-up next Friday."
results, _ := en.ParseSimple(text)

for _, r := range results {
    fmt.Printf("Position %d: '%s' = %v\n", r.Index(), r.Text(), r.Date())
}
// Output:
// Position 18: 'March 15 at 2pm' = 2024-03-15 14:00:00
// Position 52: 'next Friday' = 2024-03-22 12:00:00

Advanced Usage

Component Inspection

Determine which parts of a date were explicitly mentioned:

results, _ := en.ParseSimple("March 15 at 3pm")
comp := results[0].Start()

// Check each component
components := []kronos.Component{
    kronos.ComponentYear,
    kronos.ComponentMonth,
    kronos.ComponentDay,
    kronos.ComponentHour,
    kronos.ComponentMinute,
}

for _, c := range components {
    if val := comp.Get(c); val != nil {
        certain := "implied"
        if comp.IsCertain(c) {
            certain = "certain"
        }
        fmt.Printf("%s: %d (%s)\n", c, *val, certain)
    }
}
// Output:
// month: 3 (certain)
// day: 15 (certain)
// hour: 15 (certain)
// year: 2024 (implied)
// minute: 0 (implied)
// second: 0 (implied)
Migration Guide

If you're upgrading from an older version of Kronos, see the MIGRATION_GUIDE.md for detailed migration instructions. Note: 95%+ of users are unaffected by recent API changes and can upgrade without any code modifications.

Architecture

Kronos uses a pipeline architecture:

  1. Parsers: Pattern-based parsers scan the input text for date/time expressions
  2. Components: Extracted date/time parts are stored as components (year, month, day, etc.)
  3. Refiners: Post-processors refine and merge results (e.g., merge "tomorrow at 3pm")
  4. Results: Final parsed results with full date/time information

Each result includes:

  • The matched text and its position
  • Parsed components (with certainty tracking)
  • Implied components (filled in from reference date)
  • A constructed time.Time object

Contributing

Contributions are welcome! Please feel free to submit issues, fork the repository, and create pull requests. You must be comfy with the license (below).

License (the Unlicense)

This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.

In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to https://unlicense.org/

Kronos is inspired by excellent natural language date/time parsing libraries across many programming languages. Here are equivalent projects in major languages:

Go
  • when - Natural language date/time parser with pluggable rules and merge strategies. Supports multiple languages (EN, RU, PT_BR, ZH, NL). Parses expressions like "next wednesday at 2:25 p.m" and "tomorrow at noon".
  • go-dateparser - Port of Python's dateparser library supporting 200+ language locales. Handles relative dates like "1 min ago", "2 weeks ago", "in 2 days", "tomorrow".
  • go-naturaldate - Natural date/time parsing for human-friendly relative date/time ranges. Defaults to past direction for ambiguous expressions.
  • go-anytime - Parse natural and standardized dates/times and ranges without knowing the format in advance.
  • dateparse - Parse many date strings without knowing format in advance. While not strictly "natural language," it's very flexible for parsing various date formats automatically.
JavaScript/TypeScript
  • chrono - The original JavaScript library that inspired Kronos. Supports multiple languages (en, ja, fr, nl, ru, uk, and partial support for de, es, pt, zh.hant). Available as chrono-node on npm.
Python
  • dateparser - Comprehensive Python library supporting 200+ language locales. Parses specific dates ('5:47pm 29th of December, 2015') and relative times ('10 minutes ago'). Built on top of dateutil.parser with enhanced natural language support.
  • parsedatetime - Parses natural language dates with strong support for relative dates like 'tomorrow'. Supports multiple locales but requires manual specification.
  • timefhuman - Extracts datetimes and durations from natural language text. Supports ranges, lists, and more complex expressions.
Ruby
  • chronic - Pure Ruby natural language date parser. Handles a huge variety of date/time formats with case-insensitive parsing and common abbreviation/misspelling support.
Java
  • Natty - Natural language date parser applying standard language recognition and translation techniques. Handles complex patterns like "2 wednesdays from now".
  • PrettyTime::NLP - Wraps Natty for parsing natural language date/time expressions with integration into the PrettyTime formatting library.
C# / .NET
  • Microsoft.Recognizers.Text - Microsoft's comprehensive recognizer for dates, times, numbers, and more from natural language text.
  • Chronic .NET - .NET port of the Ruby Chronic library for natural language date parsing.
PHP
  • Carbon - Popular PHP API extension for DateTime with natural language parsing support ('tomorrow', 'next wednesday', '1 year ago'). Supports 200+ languages and 500+ regional variants.
Rust
  • dateparser - Parses date strings in commonly used formats, returning chrono DateTime objects. Supports unix timestamps, RFC formats, and various common date formats.
  • temps-chrono - Parses human-readable time expressions in multiple languages (English, German). Handles "in 2 hours", "tomorrow at 2:00 pm", "next tuesday".
Swift / iOS
  • NSDataDetector - Apple's Foundation framework class for detecting dates, times, addresses, and other data types in natural language text. Limited natural language support (works for specific dates but not relative expressions like "last week").
  • SoulverCore - Advanced natural language date input library for Mac and iOS apps.
Kotlin / Android
  • Natty - Same Java library usable in Kotlin/Android projects for natural language date parsing.
  • Hawking - Natural Language Date Time Parser that extracts date/time from text with context.
Perl
  • DateTime::Format::Natural - Parses informal natural language date/time strings into DateTime objects. Handles "tomorrow", "next Tuesday", "1 hour ago".
  • Date::Manip - Powerful date manipulation and parsing module for human-formatted dates. Most comprehensive but also largest and slowest Perl date module.
Elixir
  • Timex - Complete date/time library for Elixir with timezone support. Primarily handles structured formats (ISO 8601, RFC 1123, custom format strings) with limited natural language support.
Scala
  • nscala-time - Scala wrapper around Joda-Time with more idiomatic Scala syntax. Focused on improving expressiveness rather than advanced natural language parsing.
C++
  • Howard Hinnant's date library - Comprehensive date/time library based on C++11/14/17 <chrono>. Includes IANA timezone database parser. Focuses on format-based parsing rather than natural language.

Documentation

Overview

Package kronos provides natural language date parsing for Go.

Kronos makes it easy to parse human-friendly date expressions like "tomorrow at 3pm", "next Friday", "in 2 weeks" into structured time.Time values. It's inspired by the excellent chrono JavaScript library and provides a similar feature set with a Go-idiomatic API.

Quick Start

The simplest way to parse dates is using the en package convenience functions:

import "github.com/kljensen/kronos/en"

// Parse a single date
date, err := en.ParseDateSimple("tomorrow at 3pm")

// Parse all dates in text
results, err := en.ParseSimple("Meet me tomorrow or next Friday")

Builder Pattern API

For more control over parsing behavior, use the builder pattern:

parser := en.New().
    WithReferenceDate(refDate).
    PreferPast().
    DateOrder(kronos.DateOrderDMY)

results, err := parser.Parse("last Monday")

The builder provides a fluent API for configuration:

  • WithReferenceDate(time.Time) - Set the reference date for relative dates
  • Strict() - Enable strict parsing mode (formal patterns only)
  • Casual() - Enable casual parsing mode (informal expressions, default)
  • DateOrder(DateOrder) - Set date component order (MDY, DMY, YMD)
  • PreferPast() - Prefer past dates when ambiguous
  • PreferFuture() - Prefer future dates when ambiguous
  • PreferCurrentPeriod() - Prefer current period (default)
  • Timezone(string) - Set default timezone

Working with Results

Each Result represents a parsed date/time expression:

for _, r := range results {
    fmt.Printf("Found '%s' at position %d\n", r.Text(), r.Index())
    fmt.Printf("Date: %v\n", r.Date())

    // Access individual components
    comp := r.Start()
    if comp.IsCertain(kronos.ComponentHour) {
        hour := comp.Get(kronos.ComponentHour)
        fmt.Printf("Hour was mentioned: %d\n", *hour)
    }
}

Component Certainty

Kronos distinguishes between components that were explicitly mentioned and those that were implied from context:

results, _ := en.ParseSimple("tomorrow at 3pm")
comp := results[0].Start()

comp.IsCertain(ComponentHour)  // true - "3pm" explicitly mentions hour
comp.IsCertain(ComponentYear)  // false - year is implied from reference date

Date Ranges

Kronos can parse date ranges with both start and end dates:

results, _ := en.ParseSimple("from Monday to Friday")
if results[0].End() != nil {
    start := results[0].Start().Date()
    end := results[0].End().Date()
}

Supported Expressions

Relative dates:

  • "yesterday", "today", "tomorrow"
  • "last Monday", "next Friday"
  • "3 days ago", "in 2 weeks"
  • "2 hours from now"

Absolute dates:

  • "March 15, 2024"
  • "3/15/2024", "15/3/2024" (configurable order)
  • "2024-03-15" (ISO format)
  • "15th of March"

Time expressions:

  • "3pm", "15:30", "3:30:45 PM"
  • "noon", "midnight"
  • "14:30 EST"

Combined:

  • "tomorrow at 3pm"
  • "March 15 at 14:30"
  • "next Friday at noon"

Localization

The en package provides pre-configured parsers for English:

  • en.Casual - Casual US English (recognizes informal expressions)
  • en.Strict - Strict US English (formal patterns only)
  • en.GB - British English (DMY date order)

You can create parsers from these configurations:

parser := kronos.New(en.Casual)
parser := kronos.New(en.GB).PreferPast()

Or use the convenience builders:

parser := en.New()           // Casual US English
parser := en.StrictParser()  // Strict US English
parser := en.GBParser()      // British English

Architecture

Kronos uses a multi-stage pipeline:

1. Parsers scan the input text for date/time patterns 2. Each match is converted to Components (year, month, day, etc.) 3. Refiners post-process results (e.g., merge "tomorrow at 3pm") 4. Final Results include matched text, components, and time.Time values

Thread Safety

ParserBuilder is not thread-safe. Create a separate builder for each goroutine:

// Good: one builder per goroutine
go func() {
    parser := en.New()
    results, _ := parser.Parse(text)
}()

// Bad: shared builder across goroutines
parser := en.New()
go func() {
    results, _ := parser.Parse(text1)  // Race condition!
}()
go func() {
    results, _ := parser.Parse(text2)  // Race condition!
}()

Performance

For best performance:

  • Reuse ParserBuilder instances within a single goroutine
  • Use ParseDate() instead of Parse() if you only need the first result
  • Consider using strict mode to reduce parser overhead

Extensibility

The parser and refiner interfaces are used internally but not part of the public API. For most use cases, the pre-configured parsers (Casual, Strict, GB) provide excellent coverage. If you need additional parsing capabilities, please open an issue on GitHub.

Example (PackageLevelParse)

Example_packageLevelParse demonstrates the package-level Parse function.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos/en"
)

func main() {
	// Using the new builder API
	results, err := en.New().Parse("tomorrow")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if len(results) > 0 {
		// Tomorrow will be in the future
		isFuture := results[0].Date().After(time.Now())
		fmt.Printf("Parsed successfully: %t\n", isFuture)
	}
}
Output:

Parsed successfully: true
Example (ParserBuilder)

Example_parserBuilder demonstrates the new builder pattern API for parsing dates.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos/en"
)

func main() {
	// Basic usage with the builder pattern
	refDate := time.Date(2020, 11, 15, 12, 0, 0, 0, time.UTC)

	// Parse with default settings
	results, err := en.New().
		WithReferenceDate(refDate).
		Parse("tomorrow at 3pm")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if len(results) > 0 {
		fmt.Printf("Parsed date: %s\n", results[0].Date().Format("2006-01-02 15:04"))
	}
}
Output:

Parsed date: 2020-11-16 15:00
Example (ParserBuilder_dateOrder)

Example_parserBuilder_dateOrder demonstrates setting date order for ambiguous formats.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos"
	"github.com/kljensen/kronos/en"
)

func main() {
	refDate := time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)

	// Parse with US format (MDY)
	date, err := en.New().
		WithReferenceDate(refDate).
		DateOrder(kronos.DateOrderMDY).
		ParseDate("03/15/2020")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if date != nil {
		fmt.Printf("Month: %d, Day: %d\n", date.Month(), date.Day())
	}
}
Output:

Month: 3, Day: 15
Example (ParserBuilder_gbFormat)

Example_parserBuilder_gbFormat demonstrates UK date format parsing.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos/en"
)

func main() {
	refDate := time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)

	// GB format uses day/month/year
	date, err := en.NewGB().
		WithReferenceDate(refDate).
		ParseDate("15/03/2020")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if date != nil {
		fmt.Printf("Day: %d, Month: %d\n", date.Day(), date.Month())
	}
}
Output:

Day: 15, Month: 3
Example (ParserBuilder_preferFuture)

Example_parserBuilder_preferFuture demonstrates using PreferFuture with the builder.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos/en"
)

func main() {
	refDate := time.Date(2020, 11, 15, 12, 0, 0, 0, time.UTC)

	// Parse "March" with PreferFuture - should be March 2021
	date, err := en.New().
		WithReferenceDate(refDate).
		PreferFuture().
		ParseDate("March")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if date != nil {
		fmt.Printf("Year: %d, Month: %s\n", date.Year(), date.Month())
	}
}
Output:

Year: 2021, Month: March
Example (ParserBuilder_preferPast)

Example_parserBuilder_preferPast demonstrates using PreferPast with the builder.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos/en"
)

func main() {
	refDate := time.Date(2020, 11, 15, 12, 0, 0, 0, time.UTC)

	// Parse "March" with PreferPast - should be March 2020
	date, err := en.New().
		WithReferenceDate(refDate).
		PreferPast().
		ParseDate("March")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if date != nil {
		fmt.Printf("Year: %d, Month: %s\n", date.Year(), date.Month())
	}
}
Output:

Year: 2020, Month: March
Example (ParserBuilder_strictMode)

Example_parserBuilder_strictMode demonstrates strict parsing mode.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos/en"
)

func main() {
	refDate := time.Date(2020, 1, 1, 12, 0, 0, 0, time.UTC)

	// Strict mode validates more strictly
	date, err := en.NewStrict().
		WithReferenceDate(refDate).
		ParseDate("2020-03-15")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if date != nil {
		fmt.Printf("Parsed: %s\n", date.Format("2006-01-02"))
	}
}
Output:

Parsed: 2020-03-15
Example (ParserSimple)

Example_parserSimple demonstrates the simple convenience functions.

package main

import (
	"fmt"
	"time"

	"github.com/kljensen/kronos/en"
)

func main() {
	// Simple parsing with default settings and current time as reference
	results, err := en.ParseSimple("tomorrow")
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}

	if len(results) > 0 {
		// Tomorrow will be in the future
		isFuture := results[0].Date().After(time.Now())
		fmt.Printf("Is future: %t\n", isFuture)
	}
}
Output:

Is future: true

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func InternalAddDuration

func InternalAddDuration(ref time.Time, duration Duration) (time.Time, error)

InternalAddDuration is exported for use by internal packages only. External code should not use this function directly.

It returns the date after adding the given duration to ref. It handles fractional durations by cascading remainders to smaller units. For example, 1.5 months becomes 1 month + 2 weeks. Returns an error if the duration or resulting date is out of bounds.

func InternalGetLastWeekdayOfMonth

func InternalGetLastWeekdayOfMonth(year int, month time.Month, weekday time.Weekday, hour int) time.Time

InternalGetLastWeekdayOfMonth returns the date of the last occurrence of a given weekday in a given month and year (internal use only).

func InternalGetNthWeekdayOfMonth

func InternalGetNthWeekdayOfMonth(year int, month time.Month, weekday time.Weekday, n int, hour int) time.Time

InternalGetNthWeekdayOfMonth returns the date of the nth occurrence of a given weekday in a given month and year (internal use only).

func InternalMergeDateTimeComponent

func InternalMergeDateTimeComponent(dateComp, timeComp *parsingComponents) *parsingComponents

InternalMergeDateTimeComponent merges date and time components (internal use only).

func InternalMergeDateTimeResult

func InternalMergeDateTimeResult(dateResult, timeResult *parsingResult) *parsingResult

InternalMergeDateTimeResult merges date and time results (internal use only).

func InternalNewParsingComponents

func InternalNewParsingComponents(reference *referenceWithTimezone, knownComponents map[Component]int) *parsingComponents

InternalNewParsingComponents creates parsing components (internal use only).

func InternalNewParsingContext

func InternalNewParsingContext(text string, refDate any, option *parsingOption) *parsingContext

InternalNewParsingContext creates a parsing context (internal use only).

func InternalNewParsingResult

func InternalNewParsingResult(reference *referenceWithTimezone, index int, text string, start, end *parsingComponents) *parsingResult

InternalNewParsingResult creates a parsing result (internal use only).

func InternalNewReferenceWithTimezone

func InternalNewReferenceWithTimezone(instant time.Time, timezoneOffset *int) *referenceWithTimezone

InternalNewReferenceWithTimezone creates a reference with timezone (internal use only).

Types

type AmbiguousTimezoneMap

type AmbiguousTimezoneMap = data.AmbiguousTimezoneMap

AmbiguousTimezoneMap defines a timezone that has different offsets depending on whether daylight saving time (DST) is in effect.

This type is used with the builder pattern via WithOption for custom timezone handling:

customTimezones := kronos.TimezoneAbbrMap{
    "ET": &kronos.AmbiguousTimezoneMap{
        TimezoneOffsetDuringDst: -240,
        TimezoneOffsetNonDst: -300,
        DstStart: func(year int) time.Time { ... },
        DstEnd: func(year int) time.Time { ... },
    },
}
parser := en.New().
    WithOption(func(s *kronos.Settings) {
        s.TimezoneOverrides = customTimezones
    })

type Component

type Component string

Component represents a date/time component that can be parsed. Components are used as keys in maps, so they are string constants.

const (
	// ComponentYear represents the year component
	ComponentYear    Component = "year"
	ComponentMonth   Component = "month"
	ComponentDay     Component = "day"
	ComponentWeekday Component = "weekday"

	// ComponentHour represents the hour component
	ComponentHour        Component = "hour"
	ComponentMinute      Component = "minute"
	ComponentSecond      Component = "second"
	ComponentMillisecond Component = "millisecond"
	ComponentMicrosecond Component = "microsecond"
	ComponentNanosecond  Component = "nanosecond"

	// ComponentMeridiem represents AM/PM
	ComponentMeridiem       Component = "meridiem"
	ComponentTimezoneOffset Component = "timezoneOffset"
)

Date/time component constants

type Components

type Components interface {
	// Get returns the value of the specified component.
	// Returns nil if the component is not present (neither certain nor implied).
	//
	// For components that are either certain or implied, this returns a pointer
	// to the component's integer value. Use IsCertain to distinguish between
	// certain and implied components.
	//
	// Example:
	//
	//	if hour := comp.Get(kronos.ComponentHour); hour != nil {
	//	    fmt.Printf("Hour: %d\n", *hour)
	//	}
	Get(component Component) *int

	// IsCertain returns true if the component was explicitly mentioned in the input.
	// Returns false if the component was implied or is not present.
	//
	// This allows you to distinguish between components that were actually parsed
	// from the input vs. those that were filled in by the parser based on defaults
	// or the reference date.
	//
	// Example:
	//
	//	// For input "tomorrow at 3pm"
	//	comp.IsCertain(ComponentHour)  // true - "3pm" explicitly mentions hour
	//	comp.IsCertain(ComponentDay)   // true - "tomorrow" explicitly mentions a day
	//	comp.IsCertain(ComponentYear)  // false - year is implied from reference date
	//	comp.IsCertain(ComponentMinute) // false - minute is implied as 0
	IsCertain(component Component) bool

	// Date returns a time.Time object constructed from the components.
	// This combines all certain and implied components into a concrete date/time.
	//
	// The returned time.Time includes:
	//   - All certain components (explicitly parsed from input)
	//   - All implied components (filled in based on reference date or defaults)
	//   - Timezone adjustments if a timezone was specified
	//
	// Example:
	//
	//	date := comp.Date()
	//	fmt.Printf("Parsed date: %v\n", date)
	Date() time.Time

	// Tags returns metadata tags for these components.
	// Tags are used internally for debugging and testing to track how components were parsed.
	// For example, tags might include "result/relativeDate", "result/casualTime", "forward".
	//
	// This method is primarily useful for testing and debugging parser behavior.
	Tags() map[string]bool
}

Components represents the individual date/time parts of a parsed expression. It provides access to components like year, month, day, hour, minute, etc., and distinguishes between components that were explicitly mentioned (certain) and those that were implied or filled in by the parser.

This interface is the primary way to inspect the details of parsed date/time values.

Component Certainty:

Each component has a certainty level:

  • Certain (or Known): The component was explicitly mentioned in the input text. For example, in "March 15, 2024 at 3pm", the month (3), day (15), year (2024), and hour (15) are certain.
  • Implied: The component was not mentioned but was inferred from context or defaults. For example, in "March 15", the year might be implied from the reference date, and the hour/minute/second are implied defaults (typically 12:00:00).
  • Unknown: The component is not available. Get() returns nil for these.

Example:

parser := kronos.New(en.Casual)
results, _ := parser.Parse("tomorrow at 3pm")
comp := results[0].Start()

// Check if hour was explicitly mentioned
if comp.IsCertain(kronos.ComponentHour) {
    hour := comp.Get(kronos.ComponentHour)
    fmt.Printf("Hour was mentioned: %d\n", *hour)
}

// Get year (likely implied, not explicitly mentioned in "tomorrow")
if year := comp.Get(kronos.ComponentYear); year != nil {
    fmt.Printf("Year: %d (certain=%v)\n", *year, comp.IsCertain(kronos.ComponentYear))
}

// Convert to time.Time
date := comp.Date()

type DateOrder

type DateOrder int

DateOrder represents the order of date components in ambiguous formats.

const (
	// DateOrderMDY represents Month-Day-Year order (US format).
	DateOrderMDY DateOrder = iota
	// DateOrderDMY represents Day-Month-Year order (European format).
	DateOrderDMY
	// DateOrderYMD represents Year-Month-Day order (ISO format).
	DateOrderYMD
)

func (DateOrder) String

func (d DateOrder) String() string

String returns the string representation of DateOrder.

type DatePreference

type DatePreference int

DatePreference specifies how ambiguous dates (with missing components) should be resolved. This controls whether dates like "March 15" (without year) or "10:00" (without date) should be interpreted as being in the past, future, or current period relative to the reference time.

const (
	// PreferCurrentPeriod (default) chooses the date in the current period.
	// For dates with missing year: chooses the current year.
	// For times with missing date: chooses the current day.
	// Example: If reference is Feb 15, 2015 15:30
	//   - "March" → March 2015 (current year)
	//   - "10:00" → Feb 15, 2015 10:00 (current day, even if past)
	PreferCurrentPeriod DatePreference = iota

	// PreferPast chooses dates in the past.
	// For dates with missing year: if the date would be in the future, use previous year.
	// For times with missing date: if the time would be in the future, use previous day.
	// Example: If reference is Feb 15, 2015 15:30
	//   - "March" → March 2014 (last March, in the past)
	//   - "10:00" → Feb 15, 2015 10:00 (earlier today)
	//   - "18:00" → Feb 14, 2015 18:00 (yesterday, since 18:00 today hasn't happened yet)
	PreferPast

	// PreferFuture chooses dates in the future.
	// For dates with missing year: if the date would be in the past, use next year.
	// For times with missing date: if the time would be in the past, use next day.
	// Example: If reference is Feb 15, 2015 15:30
	//   - "March" → March 2015 (next March, in the future)
	//   - "10:00" → Feb 16, 2015 10:00 (tomorrow morning, since 10:00 already passed today)
	//   - "18:00" → Feb 15, 2015 18:00 (later today)
	PreferFuture
)

type DebugHandler

type DebugHandler func(message string)

DebugHandler is a function that handles debug events. It receives a debug message for logging or analysis.

This type is used with the builder pattern via WithOption for debug logging:

parser := en.New().
    WithOption(func(s *kronos.Settings) {
        s.DebugHandler = func(msg string) {
            log.Printf("DEBUG: %s", msg)
        }
    })

type Duration

type Duration map[Timeunit]float64

Duration represents a directed time duration as a set of values by timeunits. Positive values mean the duration goes into the future. Duration supports fractional values (e.g., 1.5 months).

func InternalReverseDuration

func InternalReverseDuration(duration Duration) Duration

InternalReverseDuration is exported for use by internal packages only. External code should not use this function directly.

It returns the reversed duration (e.g., back into the past instead of future). All values in the duration are negated.

type InternalParsingComponents

type InternalParsingComponents = parsingComponents

InternalParsingComponents is an alias for parsingComponents, allowing internal packages to use the unexported type.

type InternalParsingContext

type InternalParsingContext = parsingContext

InternalParsingContext is an alias for parsingContext, allowing internal packages to use the unexported type.

type InternalParsingOption

type InternalParsingOption = parsingOption

InternalParsingOption is an alias for parsingOption, allowing internal packages to use the unexported type.

type InternalParsingReference

type InternalParsingReference = parsingReference

InternalParsingReference is an alias for parsingReference, allowing internal packages to use the unexported type.

type InternalParsingResult

type InternalParsingResult = parsingResult

InternalParsingResult is an alias for parsingResult, allowing internal packages to use the unexported type.

type InternalParsingResultWithBoundary

type InternalParsingResultWithBoundary = parsingResultWithBoundary

InternalParsingResultWithBoundary is an alias for parsingResultWithBoundary, allowing internal packages to use the unexported type.

type InternalReferenceWithTimezone

type InternalReferenceWithTimezone = referenceWithTimezone

InternalReferenceWithTimezone is an alias for referenceWithTimezone, allowing internal packages to use the unexported type.

type Parser

type Parser = parser.Parser

Parser is the public interface for implementing custom parsers. Use this when adding support for new languages or date formats.

Example:

type MyParser struct{}
func (p *MyParser) Pattern(ctx parser.Context) *regexp.Regexp { ... }
func (p *MyParser) Extract(ctx parser.Context, match []string) any { ... }

type ParserBuilder

type ParserBuilder struct {
	// contains filtered or unexported fields
}

ParserBuilder is the main entry point for configuring and executing date parsing. It provides a fluent API for setting up parsing options and executing the parse. Use New() to create a new parser builder, configure it with builder methods, then call Parse() or ParseDate() to execute the parsing.

Thread Safety: ParserBuilder is not thread-safe and should not be shared across goroutines. Each goroutine should create its own ParserBuilder instance.

Mutability: ParserBuilder is mutable. Calling configuration methods (WithReferenceDate, Strict, PreferPast, etc.) modifies the builder's state and returns the same builder instance to enable method chaining. If you need different configurations, create separate ParserBuilder instances.

Example:

parser := kronos.New(en.Casual).
    WithReferenceDate(time.Now()).
    Strict().
    PreferPast()
result := parser.Parse("last Monday")

func New

func New(c *chrono.Chrono) *ParserBuilder

New creates a new ParserBuilder with the given Chrono configuration. This is the main entry point for the builder pattern API.

Example:

parser := kronos.New(en.Casual)

func (*ParserBuilder) Casual

func (p *ParserBuilder) Casual() *ParserBuilder

Casual enables casual parsing mode (this is the default). In casual mode, the parser accepts informal expressions and is more lenient.

func (*ParserBuilder) DateOrder

func (p *ParserBuilder) DateOrder(order DateOrder) *ParserBuilder

DateOrder sets the order of date components in ambiguous formats. Use DateOrderMDY for US format (12/31/2020), DateOrderDMY for European format (31/12/2020), or DateOrderYMD for ISO format (2020/12/31).

func (*ParserBuilder) Parse

func (p *ParserBuilder) Parse(text string) ([]Result, error)

Parse executes the parser on the given text and returns all found date/time results. Returns an error if the settings are invalid or parsing fails.

func (*ParserBuilder) ParseDate

func (p *ParserBuilder) ParseDate(text string) (*time.Time, error)

ParseDate is a shortcut for calling Parse and returning the first result's date. Returns nil if no results are found or an error occurs.

func (*ParserBuilder) PreferCurrentPeriod

func (p *ParserBuilder) PreferCurrentPeriod() *ParserBuilder

PreferCurrentPeriod configures the parser to prefer dates in the current period. This is the default behavior.

func (*ParserBuilder) PreferFuture

func (p *ParserBuilder) PreferFuture() *ParserBuilder

PreferFuture configures the parser to prefer future dates when ambiguous. For example, "March" in November would be interpreted as next March.

func (*ParserBuilder) PreferPast

func (p *ParserBuilder) PreferPast() *ParserBuilder

PreferPast configures the parser to prefer past dates when ambiguous. For example, "March" in November would be interpreted as last March.

func (*ParserBuilder) Settings

func (p *ParserBuilder) Settings() Settings

Settings returns a copy of the current settings for inspection. This is useful for advanced use cases that need to examine or validate settings. Note: Modifying the returned Settings will not affect the builder.

Example:

parser := kronos.New(en.Casual).PreferFuture()
settings := parser.Settings()
fmt.Printf("Preference: %v\n", settings.PreferDatesFrom)

func (*ParserBuilder) Strict

func (p *ParserBuilder) Strict() *ParserBuilder

Strict enables strict parsing mode. In strict mode, the parser validates more strictly and rejects ambiguous dates.

func (*ParserBuilder) Timezone

func (p *ParserBuilder) Timezone(tz string) *ParserBuilder

Timezone sets the default timezone for parsing. This timezone is used when no timezone is specified in the input.

func (*ParserBuilder) WithOption

func (p *ParserBuilder) WithOption(option func(*Settings)) *ParserBuilder

WithOption applies an advanced configuration option to the parser. This allows fine-grained control over parsing behavior by directly modifying the Settings struct.

Example:

parser := kronos.New(en.Casual).
    WithOption(func(s *kronos.Settings) {
        s.TimezoneOverrides = customTimezones
        s.DebugHandler = debugFunc
    })

func (*ParserBuilder) WithReferenceDate

func (p *ParserBuilder) WithReferenceDate(refDate time.Time) *ParserBuilder

WithReferenceDate sets the reference date for relative date calculations. This date is used as the basis for expressions like "tomorrow", "next week", etc.

type Period

type Period int

Period represents the granularity of a parsed date/time expression. It indicates the finest level of precision explicitly mentioned in the input. For example:

  • "2020" has year-level precision
  • "March 2020" has month-level precision
  • "yesterday" has day-level precision
  • "10:30" has time-level precision
const (
	// PeriodUnknown indicates the period could not be determined.
	PeriodUnknown Period = iota
	// PeriodYear represents year-level precision (e.g., "2020", "last year").
	PeriodYear
	// PeriodMonth represents month-level precision (e.g., "March", "3 months ago").
	PeriodMonth
	// PeriodWeek represents week-level precision (e.g., "last week", "2 weeks ago").
	PeriodWeek
	// PeriodDay represents day-level precision (e.g., "yesterday", "March 15").
	PeriodDay
	// PeriodTime represents time-level precision (e.g., "10:30", "2 hours ago").
	PeriodTime
)

func InternalDeterminePeriodFromDuration

func InternalDeterminePeriodFromDuration(duration Duration) Period

InternalDeterminePeriodFromDuration determines period from duration (internal use only).

func (Period) String

func (p Period) String() string

String returns the string representation of the Period.

type Refiner

type Refiner = parser.Refiner

Refiner is the public interface for implementing custom refiners. Use this when adding custom post-processing logic.

Example:

type MyRefiner struct{}
func (r *MyRefiner) Refine(ctx parser.Context, results []parser.Result) []parser.Result { ... }

type Result

type Result interface {
	// Text returns the matched text from the input.
	// This is the substring that was recognized as a date/time expression.
	//
	// Example: For input "tomorrow at 3pm", this might return "tomorrow at 3pm"
	Text() string

	// Index returns the position in the input text where this result was found.
	// The index is 0-based and represents the starting position of the matched text.
	//
	// Example: For input "Meet me tomorrow", this would return 8
	Index() int

	// Date returns a time.Time object created from the start components.
	// This is a convenience method equivalent to calling Start().Date().
	// For date ranges, this returns the start date of the range.
	//
	// The returned time.Time includes all parsed and implied components.
	// Components that were not mentioned are filled in with reasonable defaults
	// based on the reference date.
	Date() time.Time

	// Start returns the starting date/time components.
	// For a single date/time (not a range), this contains all the parsed information.
	// For a date range, this represents the beginning of the range.
	//
	// Use the Components interface to inspect individual date/time parts
	// and determine which were explicitly mentioned vs. implied.
	Start() Components

	// End returns the ending date/time components for a range.
	// Returns nil for a single date/time (not a range).
	//
	// Example: For "from Monday to Friday", Start() returns Monday's components
	// and End() returns Friday's components.
	End() Components

	// Tags returns metadata tags for this result.
	// Tags are used internally for debugging and testing to track how a result was parsed.
	// For example, tags might include "parser/iso", "refiner/merged", "result/relativeDate".
	//
	// This method is primarily useful for testing and debugging parser behavior.
	Tags() map[string]bool
}

Result represents a parsed date/time expression found in text. It provides access to the matched text, its position, and the parsed date/time value. A Result can represent either a single date/time or a range (with Start and End components).

This is the primary interface for working with parsed results in the new builder-based API. It exposes only what users need while hiding internal implementation details.

Example:

parser := kronos.New(en.Casual)
results, _ := parser.Parse("Meet me tomorrow at 3pm")
for _, r := range results {
    fmt.Printf("Found '%s' at index %d\n", r.Text(), r.Index())
    fmt.Printf("Date: %v\n", r.Date())
    if r.Start().IsCertain(kronos.ComponentHour) {
        hour := r.Start().Get(kronos.ComponentHour)
        fmt.Printf("Hour explicitly mentioned: %d\n", *hour)
    }
}

type Settings

type Settings struct {
	// Date interpretation
	DateOrder       DateOrder      // Order of date components (MDY, DMY, YMD)
	PreferDatesFrom DatePreference // Past, Future, CurrentPeriod

	// Timezone handling
	Timezone string // Default timezone name (e.g., "UTC", "America/New_York")

	// Parsing behavior
	StrictParsing bool // Validate strictly - reject ambiguous dates

	// Advanced configuration
	TimezoneOverrides TimezoneAbbrMap // Custom timezone abbreviations
	DebugHandler      DebugHandler    // Debug callback for parsing events
}

Settings contains all configuration for date parsing. It provides a comprehensive way to customize parsing behavior.

Most users should use the fluent builder API for common options:

parser := en.New().
    DateOrder(kronos.DateOrderDMY).
    PreferFuture().
    Strict()

For advanced options, use WithOption to modify settings directly:

parser := en.New().
    WithOption(func(s *kronos.Settings) {
        s.TimezoneOverrides = customTimezones
        s.DebugHandler = debugFunc
    })

func DefaultSettings

func DefaultSettings() Settings

DefaultSettings returns sensible default settings that maintain backward compatibility with existing behavior.

type Timeunit

type Timeunit string

Timeunit represents a unit of time for calculations and operations. This type is used as a key in the Duration map type for specifying time durations.

Note: While Timeunit is part of the public API (used by Duration), it is primarily an implementation detail. Future versions may move this to a more restricted scope while maintaining backward compatibility for Duration operations.

const (
	// TimeunitDecade represents a decade (10 years)
	TimeunitDecade  Timeunit = "decade"
	TimeunitYear    Timeunit = "year"
	TimeunitQuarter Timeunit = "quarter"
	TimeunitMonth   Timeunit = "month"

	// TimeunitWeek represents a week (7 days)
	TimeunitWeek Timeunit = "week"
	TimeunitDay  Timeunit = "day"

	// TimeunitHour represents an hour
	TimeunitHour        Timeunit = "hour"
	TimeunitMinute      Timeunit = "minute"
	TimeunitSecond      Timeunit = "second"
	TimeunitMillisecond Timeunit = "millisecond"
	TimeunitMicrosecond Timeunit = "microsecond"
	TimeunitNanosecond  Timeunit = "nanosecond"
)

Time unit constants for use with Duration type. These constants are required for working with Duration maps.

Example:

duration := kronos.Duration{
    kronos.TimeunitDay: 5,
    kronos.TimeunitHour: 3,
}

type TimezoneAbbrMap

type TimezoneAbbrMap = data.TimezoneAbbrMap

TimezoneAbbrMap maps timezone abbreviations to their offsets. Values can be either a simple offset (in minutes) or an AmbiguousTimezoneMap for timezones that observe DST.

This type is used with the builder pattern via WithOption for custom timezone handling:

customTimezones := kronos.TimezoneAbbrMap{
    "CUSTOM": 123,  // UTC+2:03
    "TEST": -456,   // UTC-7:36
}
parser := en.New().
    WithOption(func(s *kronos.Settings) {
        s.TimezoneOverrides = customTimezones
    })

Directories

Path Synopsis
Package en provides English language support for Chrono.
Package en provides English language support for Chrono.
examples
basic command
Package main demonstrates basic usage of the kronos date parser.
Package main demonstrates basic usage of the kronos date parser.
british command
Package main demonstrates British English date parsing.
Package main demonstrates British English date parsing.
builder_api command
Package main demonstrates the builder API for kronos.
Package main demonstrates the builder API for kronos.
components command
Package main demonstrates working with date/time components.
Package main demonstrates working with date/time components.
configured command
Package main demonstrates using the ParserBuilder to configure parsing behavior.
Package main demonstrates using the ParserBuilder to configure parsing behavior.
ranges command
Package main demonstrates working with date ranges.
Package main demonstrates working with date ranges.
chrono
Package chrono provides the internal Advanced API structures for Kronos.
Package chrono provides the internal Advanced API structures for Kronos.
common/parsers
Package parsers provides shared utilities and parsers for date/time parsing.
Package parsers provides shared utilities and parsers for date/time parsing.
common/refiners
Package refiners provides common refiner utilities for post-processing parsing results.
Package refiners provides common refiner utilities for post-processing parsing results.
en/data
Package data provides English language dictionaries, patterns, and parsing functions.
Package data provides English language dictionaries, patterns, and parsing functions.
en/refiners
Package refiners provides English-specific refiners for post-processing parsing results.
Package refiners provides English-specific refiners for post-processing parsing results.
helpers
Package helpers provides internal utility functions for kronos date parsing.
Package helpers provides internal utility functions for kronos date parsing.
parsing
Package parsing contains internal parsing implementation types for kronos.
Package parsing contains internal parsing implementation types for kronos.
Package parser defines the interfaces for implementing custom parsers and refiners in Kronos.
Package parser defines the interfaces for implementing custom parsers and refiners in Kronos.

Jump to

Keyboard shortcuts

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