A Go library for parsing, validating, and manipulating Calendar Versioning (CalVer) strings according to the CalVer specification.
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.
- 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
go get github.com/shazib-summar/go-calver
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)
}
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.
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 | 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>.*) |
Complete examples files can be found in the examples dir
// 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)
}
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)
}
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")
}
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
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())
}
collection, err := calver.NewCollectionWithOptions(
[]string{"2025-07-14", "2025.07.15"},
calver.WithFormat("<YYYY>-<MM>-<DD>", "<YYYY>.<MM>.<DD>"),
)
// 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
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"
// 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
Run the test suite:
go test ./...
Run tests with coverage:
go test -cover ./...
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.
- Fork the repository
- Clone your fork:
git clone https://github.com/yourusername/go-calver.git
- Create a feature branch:
git checkout -b feature/amazing-feature
- Make your changes and add tests
- Run tests:
go test ./...
- Commit your changes with a DCO signature:
git commit -s -m 'Add amazing feature'
- Push to the branch:
git push origin feature/amazing-feature
- Open a Pull Request
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.
- 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.