Skip to content

Chiyoshic/go-extclock

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

36 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

go-extclock

CI Go Reference Go Version License Latest Release

A Go library for extended-hour clocks and business-day date/time handling.

What is go-extclock?

An extended-hour clock can represent values beyond 24:00, such as 26:30 or 30:00. This is useful when a business day does not end at midnight.

With the default cutoff hour of 6, wall-clock 2026-07-05 02:30 is represented as business 2026-07-04 26:30. With the default system, the valid clock range is from 00:00:00 through 30:00:00.

When to use this library

Use this package when your domain represents a business day with extended-hour values such as 26:30 or 30:00.

Do not use it as a replacement for time.Time when you only need ordinary wall-clock timestamps.

Installation

go get github.com/Chiyoshic/go-extclock

Requirements

go-extclock requires Go 1.20 or later. CI tests Go 1.20.x through the latest stable Go release.

Full API documentation is available on pkg.go.dev.

Quick start

package main

import (
	"fmt"
	"log"
	"time"

	"github.com/Chiyoshic/go-extclock"
)

func main() {
	clock, err := extclock.ParseClock("26:30")
	if err != nil {
		log.Fatal(err)
	}

	dt, err := extclock.ParseDateTime("2026-07-04 26:30")
	if err != nil {
		log.Fatal(err)
	}

	wall := time.Date(2026, time.July, 5, 2, 30, 0, 0, time.UTC)
	business, err := extclock.FromTime(wall)
	if err != nil {
		log.Fatal(err)
	}

	back, err := extclock.DefaultSystem().ToTime(dt, time.UTC)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(clock.StringHM())
	fmt.Println(business.StringHM())
	fmt.Println(back.Format(time.RFC3339))
}

Output:

26:30
2026-07-04 26:30
2026-07-05T02:30:00Z

Core concepts

Clock

Clock is a date-less extended-hour clock. It has no date and no timezone. Its validity depends on a System: the default system accepts values up to 30:00:00, while custom systems can use other maximum hours. Package-level constructors such as NewClock use DefaultSystem().

Date

Date is a calendar date only. It has no clock and no timezone. Dates are validated against the library's supported year range, and invalid calendar dates such as 2026-02-31 are rejected.

DateTime

DateTime is a business date paired with an extended-hour clock. Its clock validity depends on a System. It represents business-day time, not necessarily a normalized wall-clock instant.

System

System owns cutoff-hour-specific rules. The default system uses cutoff hour 6, so MaxHour() is 30. In general, MaxHour() is 24 + CutoffHour(). Use System methods when working with custom cutoff rules.

Default system

Package-level functions use DefaultSystem(). Use them when the default cutoff hour of 6 matches your domain.

package main

import (
	"log"
	"time"

	"github.com/Chiyoshic/go-extclock"
)

func main() {
	clock, err := extclock.NewClock(26, 30, 0, 0)
	if err != nil {
		log.Fatal(err)
	}

	parsedClock, err := extclock.ParseClock("26:30")
	if err != nil {
		log.Fatal(err)
	}

	parsedDateTime, err := extclock.ParseDateTime("2026-07-04 26:30")
	if err != nil {
		log.Fatal(err)
	}

	business, err := extclock.FromTime(time.Date(2026, time.July, 5, 2, 30, 0, 0, time.UTC))
	if err != nil {
		log.Fatal(err)
	}

	_, _, _, _ = clock, parsedClock, parsedDateTime, business
}

Custom systems

Create a Config and then a System when your business day uses a different cutoff hour. The maximum clock is derived from the cutoff hour.

package main

import (
	"log"
	"time"

	"github.com/Chiyoshic/go-extclock"
)

func main() {
	cfg, err := extclock.NewConfig(10)
	if err != nil {
		log.Fatal(err)
	}

	s, err := extclock.NewSystem(cfg)
	if err != nil {
		log.Fatal(err)
	}

	clock, err := s.ParseClock("33:30")
	if err != nil {
		log.Fatal(err)
	}

	dt, err := s.NewDateTime(extclock.MustDate(2026, time.July, 4), clock)
	if err != nil {
		log.Fatal(err)
	}

	if err := s.ValidateDateTime(dt); err != nil {
		log.Fatal(err)
	}
}

With cutoff hour 10, the maximum clock is 34:00:00.

Parsing and formatting

Most parse functions have an Auto layout variant. Auto layout detects the input shape. For formatting, Auto uses the package-defined default behavior for that value type.

Clock inputs:

Input Meaning
26:30 hour and minute
26:30:00 hour, minute, second
26:30:00.123456789 nanosecond precision

Date inputs:

Input Meaning
2026-07-04 ISO date
20260704 compact date

DateTime inputs:

Input Meaning
2026-07-04 26:30 space separator
2026-07-04T26:30 T separator
2026-07-04 26:30:00.123456789 nanosecond precision

Formatting methods include Clock.Format, Date.Format, DateTime.Format, and their package-level or System variants. System formatting methods validate under that system before formatting.

JSON and text encoding

Clock, Date, and DateTime implement text and JSON encoding. JSON values are strings:

{
  "starts_at": "2026-07-04 26:30:00"
}

Text and JSON unmarshaling for Clock and DateTime is not tied to a specific System. When system-specific validation matters, call System.ValidateClock or System.ValidateDateTime after decoding.

Business-day conversion

System.FromTime converts wall-clock time to business DateTime. System.ToTime converts business DateTime back to wall-clock time. System.DayRange and System.DayRangeDate return a half-open business-day range [start, end).

Default cutoff examples:

2026-07-05 02:30 -> 2026-07-04 26:30
2026-07-05 06:30 -> 2026-07-05 06:30

Duration arithmetic

Clock duration operations come in two styles:

  • Bounded clock operations do not cross the business-day boundary.
  • Shift clock operations may cross the boundary and return a dayOffset.

DateTime duration operations may cross business days directly and return a new DateTime. Bounded DateTime duration operations reject crossing.

29:30 + 30m -> 30:00
29:30 + 1h  -> error for bounded clock add
29:30 + 1h  -> 06:30, dayOffset 1 for clock shift

2026-07-04 29:30 + 1h -> 2026-07-05 06:30

Relevant APIs include System.AddClockDurationBounded, System.SubClockDurationBounded, System.ShiftClockDuration, System.UnshiftClockDuration, System.AddDateTimeDuration, and System.AddDateTimeDurationBounded.

Comparison semantics

DateTime.Compare is a pure value comparison: it compares the stored business date and clock lexicographically. System.CompareDateTime is system-aware: it compares normalized wall-clock instants.

Under the default system, these two values may represent the same wall-clock instant:

2026-07-04 30:00
2026-07-05 06:00

That means pure value comparison and system-aware comparison can differ.

package main

import (
	"fmt"

	"github.com/Chiyoshic/go-extclock"
)

func main() {
	s := extclock.DefaultSystem()
	a := s.MustDateTime(extclock.MustDate(2026, 7, 4), s.MustClock(30, 0, 0, 0))
	b := s.MustDateTime(extclock.MustDate(2026, 7, 5), s.MustClock(6, 0, 0, 0))

	fmt.Println(a.Compare(b) == 0)

	n, err := s.CompareDateTime(a, b)
	if err != nil {
		panic(err)
	}
	fmt.Println(n == 0)
}

Errors

The package exposes sentinel errors for category checks:

  • ErrInvalidConfig
  • ErrInvalidClock
  • ErrInvalidDate
  • ErrInvalidDateTime
  • ErrInvalidLayout
  • ErrParseClock
  • ErrParseDate
  • ErrParseDateTime
  • ErrOutOfRange

Parsing failures may be returned as *ParseError. Use errors.Is to check error categories and wrapped validation causes. Do not rely on exact error strings.

package main

import (
	"errors"
	"fmt"
	"time"

	"github.com/Chiyoshic/go-extclock"
)

func main() {
	_, err := extclock.ClockFromDuration(31 * time.Hour)
	if errors.Is(err, extclock.ErrOutOfRange) {
		fmt.Println("range error")
	}
}

Edge cases and caveats

  • With the default system, 30:00:00 is valid and 30:00:00.000000001 is invalid.
  • In any system, MaxHour():00:00 is valid; values after that boundary are invalid.
  • Date supports years 1 through 9999. The zero value Date{} is invalid.
  • Clock and DateTime do not carry a timezone.
  • DateTime values near the maximum supported date may validate but still fail conversion to wall-clock time if extended-hour normalization would move past year 9999.
  • Very large date/time differences can exceed time.Duration range and return ErrOutOfRange.
  • DayRange and DayRangeDate use calendar-day arithmetic in the provided location; daylight-saving transitions can affect elapsed wall-clock duration.

Testing

go test ./...

CI runs formatting, go vet, tests on Go 1.20.x through stable, and race tests on stable Go.

License

go-extclock is licensed under the MIT License. See LICENSE.

About

A Go library for extended-hour clocks and business-day date/time handling, with support for custom cutoff-hour systems.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages