Skip to content

shazib-summar/go-calver

Repository files navigation

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.

About

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

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •