calver

package module
v0.0.3 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2025 License: Apache-2.0 Imports: 6 Imported by: 0

README

Go Reference Go Report Card Tests Status Contributor Covenant

go-calver

A Go library for parsing, validating, and manipulating Calendar Versioning (CalVer) strings according to the CalVer specification.

What is CalVer?

Calendar Versioning (CalVer) is a versioning scheme that uses calendar dates for version numbers. Unlike Semantic Versioning (SemVer) which focuses on API compatibility, CalVer emphasizes when something was released, making it ideal for projects that release frequently or on a schedule.

Features

  • Flexible Format Support: Supports all standard CalVer conventions including <YYYY>, <YY>, <0Y>, <MM>, <0M>, <WW>, <0W>, <DD>, <0D>, <MINOR>, <MICRO>, and <MODIFIER>
  • Format Validation: Ensures version strings match their specified format
  • Comparison Operations: Compare CalVer versions with proper precedence handling
  • Collections: Sort and manage collections of CalVer objects
  • Version Incrementing: Increment major, minor, micro, and modifier versions while preserving zero-padding
  • Series Management: Extract version series at different levels (major, minor, micro, modifier)
  • Comprehensive Testing: Extensive test coverage for all functionality
  • Unlimited Format Support: Supports any format string since users control the format - the only requirement is to use the CalVer conventions correctly

Installation

go get github.com/shazib-summar/go-calver

Quick Start

package main

import (
	"fmt"
	"log"

	"github.com/shazib-summar/go-calver"
)

func main() {
	format := "Rel-<YYYY>-<0M>-<0D>"
	// Create a new Version object
	ver, err := calver.Parse(format, "Rel-2025-07-14")
	if err != nil {
		log.Fatal(err)
	}

	// Print the version
	fmt.Println(ver.String()) // Output: Rel-2025-07-14

	// Compare with another version
	other, _ := calver.Parse(format, "Rel-2025-07-15")
	result := ver.Compare(other)
	fmt.Printf("Comparison result: %d\n", result) // Output: -1 (less than)
}

Supported Formats

The library supports all standard CalVer conventions, organized into four levels that determine the order when comparing versions. Only one convention string may be used per level in the format string provided to NewVersion func.

Levels and Conventions
Level Description Conventions Example
Major Primary version identifier <YYYY>, <YY>, <0Y>, <MAJOR> 2025, 25, 05, 12
Minor Secondary version identifier <MM>, <0M>, <MINOR> 7, 07, 14
Micro Tertiary version identifier <WW>, <0W>, <DD>, <0D>, <MICRO> 1, 01, 31, 42
Modifier Additional version metadata <MODIFIER> alpha, beta, 12:43
Convention Details
Convention Description Regex
<YYYY> 4-digit year (?P<major>\d{4})
<YY> 1-2 digit year (?P<major>\d{1,2})
<0Y> 2-digit year (zero-padded) (?P<major>\d{2})
<MAJOR> Major version number (?P<major>\d+)
<MM> 1-2 digit month (?P<minor>\d{1,2})
<0M> 2-digit month (zero-padded) (?P<minor>\d{2})
<MINOR> Minor version number (?P<minor>\d+)
<WW> 1-2 digit week (?P<micro>\d{1,2})
<0W> 2-digit week (zero-padded) (?P<micro>\d{2})
<DD> 1-2 digit day (?P<micro>\d{1,2})
<0D> 2-digit day (zero-padded) (?P<micro>\d{2})
<MICRO> Micro version number (?P<micro>\d+)
<MODIFIER> Modifier string or additional version part (?P<modifier>.*)

Usage Examples

Complete examples files can be found in the examples dir

Basic Version Creation
// Year-Month-Day format
ver, err := calver.Parse("<YYYY>-<MM>-<DD>", "2025-07-14")
if err != nil {
    log.Fatal(err)
}

// Year.Release format
ver, err = calver.Parse("<YYYY>.R<DD>", "2025.R14")
if err != nil {
    log.Fatal(err)
}

// Ubuntu-style format
ver, err = calver.Parse("<0Y>.<0M>.<DD>", "22.04.6")
if err != nil {
    log.Fatal(err)
}
Creating Version objects with multiple Formats

The following example shows how to create a Version object with multiple formats. In this case, the format that matches the version string will be used.

ver, err := calver.ParseWithOptions(
    "2025-07-14",
    calver.WithFormat("<YYYY>-<MM>-<DD>", "<YYYY>.<MM>.<DD>"),
)
if err != nil {
    log.Fatal(err)
}
Version Comparison
verA, _ := calver.Parse("<YYYY>-<MM>-<DD>", "2025-07-14")
verB, _ := calver.Parse("<YYYY>.<MM>.<DD>", "2025.07.15")

result := verA.Compare(verB)
switch result {
case -1:
    fmt.Println("verA is older than verB")
case 0:
    fmt.Println("verA equals verB")
case 1:
    fmt.Println("verA is newer than verB")
}
Additional Helper functions
verA, _ := calver.Parse("<YYYY>-<MM>-<DD>", "2025-07-14")
verB, _ := calver.Parse("<YYYY>-<MM>-<DD>", "2025-07-15")

// Check equality
fmt.Println(verA.Equal(verB))        // false

// Check ordering
fmt.Println(verA.LessThan(verB))     // true
fmt.Println(verA.GreaterThan(verB))  // false

// Check inclusive ordering
fmt.Println(verA.LessThanOrEqual(verB))     // true
fmt.Println(verA.GreaterThanOrEqual(verB))   // false
Working with Collections
versions := []string{
    "2025-07-14",
    "2025-07-15",
    "2025-07-13",
}

collection, err := calver.NewCollection("<YYYY>-<MM>-<DD>", versions...)
if err != nil {
    log.Fatal(err)
}

// Sort the collection
sort.Sort(collection)

// Print sorted versions
for _, v := range collection {
    fmt.Println(v.String())
}
Working with Collections with multiple Formats
collection, err := calver.NewCollectionWithOptions(
    []string{"2025-07-14", "2025.07.15"},
    calver.WithFormat("<YYYY>-<MM>-<DD>", "<YYYY>.<MM>.<DD>"),
)
Version Incrementing
// Create a version
ver, err := calver.Parse("<YYYY>.<0M>.<0D>", "2025.07.14")
if err != nil {
    log.Fatal(err)
}

// Increment different parts
err = ver.IncMajor()   // 2025 -> 2026
err = ver.IncMinor()   // 07 -> 08
err = ver.IncMicro()   // 14 -> 15

fmt.Println(ver.String()) // Output: 2026.08.15

// Zero-padding is preserved
ver, _ = calver.Parse("<YYYY>.<0M>.<0D>", "2025.01.09")
err = ver.IncMinor()   // 01 -> 02 (preserves zero-padding)
err = ver.IncMicro()   // 09 -> 10 (loses zero-padding)

fmt.Println(ver.String()) // Output: 2025.02.10
Series Management
ver, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    log.Fatal(err)
}

// Get series at different levels
fmt.Println(ver.Series("major"))    // Output: Rel-2025
fmt.Println(ver.Series("minor"))    // Output: Rel-2025-07
fmt.Println(ver.Series("micro"))    // Output: Rel-2025-07-14
fmt.Println(ver.Series("modifier")) // Output: Rel-2025-07-14
fmt.Println(ver.Series(""))         // Output: Rel-2025-07-14 (full version)

// Useful for grouping related versions
majorSeries := ver.Series("major") // "Rel-2025"
minorSeries := ver.Series("minor") // "Rel-2025-07"
Custom Format with Modifiers
// Release format with timestamp modifier
format := "RELEASE.<YYYY>-<0M>-<0D>T<MODIFIER>Z"
version := "RELEASE.2025-07-23T15-54-02Z"

ver, err := calver.Parse(format, version)
if err != nil {
    log.Fatal(err)
}

fmt.Println(ver.String()) // Output: RELEASE.2025-07-23T15-54-02Z

Testing

Run the test suite:

go test ./...

Run tests with coverage:

go test -cover ./...

Contributing

Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.

Development Setup
  1. Fork the repository
  2. Clone your fork: git clone https://github.com/yourusername/go-calver.git
  3. Create a feature branch: git checkout -b feature/amazing-feature
  4. Make your changes and add tests
  5. Run tests: go test ./...
  6. Commit your changes with a DCO signature: git commit -s -m 'Add amazing feature'
  7. Push to the branch: git push origin feature/amazing-feature
  8. Open a Pull Request

License

This project is licensed under the Apache License 2.0 - see the LICENSE file for details.

Acknowledgments

  • CalVer.org for the Calendar Versioning specification
  • The Go community for best practices and testing patterns
  • My playful niece Abigail without whom this would've been done much sooner.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func WithFormat

func WithFormat(formats ...string) parseOption

WithFormat is a parse option that specifies the format string that should be used to parse the version string.

Example:

ver, err := ParseWithOptions("2025.07.14", WithFormat("<YYYY>.<0M>.<0D>"))
if err != nil {
    return err
}
fmt.Println(ver.String()) // 2025.07.14

If there are more than one possible format that may match the version string, this function can be used with the WithFormat option.

Example:

formats := []string{
    "<YYYY>.<0M>.<0D>",
    "<YYYY>.<0M>.<0D>-<MODIFIER>",
}
ver, err := ParseWithOptions("2025.07.14", WithFormat(formats...))
if err != nil {
    return err
}
fmt.Println(ver.String()) // 2025.07.14

If multiple formats are provided, the format that matches the version string will be used. For example, in the following code, the format `"Rel-<YYYY>-<0M>-<0D>"` will be used at it matches the version string while the other formats do not.

ver, err := ParseWithOptions(
    "Rel-2025-07-14",
    WithFormat(
        "Rel-<YYYY>",
        "Rel-<YYYY>-<0M>",
        "Rel-<YYYY>-<0M>-<0D>",
    )
)
if err != nil {
    return err
}
fmt.Println(ver.String()) // Rel-2025-07-14

Types

type Collection

type Collection []*Version

Collection is a collection of Version objects. It implements the sort.Interface interface.

func NewCollection

func NewCollection(format string, versions ...string) (Collection, error)

NewCollection creates a new Collection from a format string and a list of versions. It will return an error if any of the versions do not match the format.

Example:

collection, err := calver.NewCollection(
    "<YYYY>.<0M>.<0D>",
    "2025.01.18",
    "2023.07.14",
    "2025.03.16",
)
if err != nil {
    return err
}

This is the same as calling `NewCollectionWithOptions(versions, WithFormat(format))`. Note that `WithFormat` option can take a list of formats.

func NewCollectionWithOptions

func NewCollectionWithOptions(versions []string, opts ...parseOption) (Collection, error)

NewCollectionWithOptions creates a new `Collection` from a list of versions and a list of parse options. It will return an error if any of the versions do not match (any of) the format or if no options are provided.

Example:

collection, err := calver.NewCollectionWithOptions(
    []string{"2025.01.18", "2023.07.14", "2025.03.16"},
    calver.WithFormat("<YYYY>.<0M>.<0D>"),
)
if err != nil {
    return err
}

If there are more than one possible format that may match the version string, this function can be used with the `WithFormat` option.

Example:

formats := []string{
    "<YYYY>.<0M>.<0D>",
    "<YYYY>.<0M>.<0D>-<MODIFIER>",
}
collection, err := calver.NewCollectionWithOptions(
    []string{"2025.01.18", "2023.07.14", "2025.03.16"},
    calver.WithFormat(formats...),
)
if err != nil {
    return err
}

func (Collection) Len

func (c Collection) Len() int

func (Collection) Less

func (c Collection) Less(i, j int) bool

func (Collection) Swap

func (c Collection) Swap(i, j int)

type Version

type Version struct {
	// Format is the original format string. If multiple formats were provided,
	// this will be the format that matched the version string.
	Format string
	// Major is the major version. This is guaranteed to be a number.
	Major string
	// Minor is the minor version. This is guaranteed to be a number.
	Minor string
	// Micro is the micro version. This is guaranteed to be a number.
	Micro string
	// Modifier is the modifier version. This can be a number or a string.
	Modifier string
}

Version is the object representing a CalVer version. To get the string representation of the Version, use the String method.

func Parse

func Parse(format string, version string) (*Version, error)

Parse creates a new Version object from a format string and a version. The format string is expected to follow the conventions defined in ConventionsRegex.

Example:

ver, err := Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
fmt.Println(ver.String()) // Rel-2025-07-14

This is the same as calling `ParseWithOptions(version, WithFormat(format))`

func ParseWithOptions

func ParseWithOptions(version string, opts ...parseOption) (*Version, error)

ParseWithOptions creates a new Version object from a version string and a list of parse options.

Example:

ver, err := ParseWithOptions(
    "Rel-2025-07-14",
    WithFormat("Rel-<YYYY>-<0M>-<0D>"),
)
if err != nil {
    return err
}
fmt.Println(ver.String()) // Rel-2025-07-14

If there are more than one possible format that may match the version string, this function can be used with the WithFormat option.

Example:

formats := []string{
    "Rel-<YYYY>-<0M>-<0D>",
    "<YYYY>.<0M>.<0D>",
}
ver, err := ParseWithOptions("Rel-2025-07-14", WithFormat(formats...))
if err != nil {
    return err
}
fmt.Println(ver.String()) // Rel-2025-07-14

func (*Version) Compare

func (c *Version) Compare(v *Version) int

Compare returns 0 if the versions are equal, -1 if the current version is less than the other version, and 1 if the current version is greater than the other version.

Example:

ver1, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
ver2, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-15")
if err != nil {
    return err
}
fmt.Printf("%d\n", ver1.Compare(ver2)) // -1

The comparison is done in the following order: major, minor, micro, modifier. Major, minor and micro are compared as integers whereas the modifier is compared as integer if it is a number otherwise as a string.

func (*Version) Equal

func (c *Version) Equal(v *Version) bool

Equal reports whether the version is equal to the other version. Example:

ver1, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
ver2, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-15")
if err != nil {
    return err
}
fmt.Printf("%t\n", ver1.Equal(ver2)) // false

func (*Version) GetFormat

func (c *Version) GetFormat() string

GetFormat returns the original format string.

func (*Version) GetMajor

func (c *Version) GetMajor() string

GetMajor returns the major version.

func (*Version) GetMicro

func (c *Version) GetMicro() string

GetMicro returns the micro version.

func (*Version) GetMinor

func (c *Version) GetMinor() string

GetMinor returns the minor version.

func (*Version) GetModifier

func (c *Version) GetModifier() string

GetModifier returns the modifier version.

func (*Version) GreaterThan

func (c *Version) GreaterThan(v *Version) bool

GreaterThan reports whether the version is greater than the other version. Example:

ver1, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
ver2, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-15")
if err != nil {
    return err
}
fmt.Printf("%t\n", ver1.GreaterThan(ver2)) // false

func (*Version) GreaterThanOrEqual

func (c *Version) GreaterThanOrEqual(v *Version) bool

GreaterThanOrEqual reports whether the version is greater than or equal to the other version. Example:

ver1, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
ver2, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-15")
if err != nil {
    return err
}
fmt.Printf("%t\n", ver1.GreaterThanOrEqual(ver2)) // false

func (*Version) IncMajor

func (c *Version) IncMajor() error

IncMajor increments the major version. If the major version is 0 padded it will retain the 0 padding unless the major version is of the form 09 or 099 or 0999 and so on.

func (*Version) IncMicro

func (c *Version) IncMicro() error

IncMicro increments the micro version. If the micro version is 0 padded it will retain the 0 padding unless the micro version is of the form 09 or 099 or 0999 and so on.

func (*Version) IncMinor

func (c *Version) IncMinor() error

IncMinor increments the minor version. If the minor version is 0 padded it will retain the 0 padding unless the minor version is of the form 09 or 099 or 0999 and so on.

func (*Version) IncModifier

func (c *Version) IncModifier() error

IncModifier increments the modifier version. If the modifier version is 0 padded it will retain the 0 padding unless the modifier version is of the form 09 or 099 or 0999 and so on.

It will return an error if the modifier is not a number.

func (*Version) LessThan

func (c *Version) LessThan(v *Version) bool

LessThan reports whether the version is less than the other version. Example:

ver1, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
ver2, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-15")
if err != nil {
    return err
}
fmt.Printf("%t\n", ver1.LessThan(ver2)) // true

func (*Version) LessThanOrEqual

func (c *Version) LessThanOrEqual(v *Version) bool

LessThanOrEqual reports whether the version is less than or equal to the other version. Example:

ver1, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
ver2, err := calver.Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-15")
if err != nil {
    return err
}
fmt.Printf("%t\n", ver1.LessThanOrEqual(ver2)) // true

func (*Version) Series

func (c *Version) Series(level string) string

Series returns the series of the Version object. The series determined using the provided level. For example, if the level is major, the series will be the major version. If the level is minor, the series will be the major and minor version and so on.

If no level or an unrecognized level is provided, the series will be the entire version string.

Example:

ver, err := Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
fmt.Println(ver.Series("major"))    // Rel-2025
fmt.Println(ver.Series("minor"))    // Rel-2025-07
fmt.Println(ver.Series("micro"))    // Rel-2025-07-14
fmt.Println(ver.Series("modifier")) // Rel-2025-07-14-0
fmt.Println(ver.Series(""))         // Rel-2025-07-14

func (*Version) String

func (c *Version) String() string

String returns the Version object as a string. The string will be in the format of the original format string.

Example:

ver, err := Parse("Rel-<YYYY>-<0M>-<0D>", "Rel-2025-07-14")
if err != nil {
    return err
}
fmt.Println(ver.String()) // Rel-2025-07-14

Directories

Path Synopsis
examples
collection command
compare command
newversion command
series command

Jump to

Keyboard shortcuts

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