A lightweight, memory-efficient Go package for working with dates without time components. The bublyk package introduces the Date type, specifically designed for applications that only need calendar dates in UTC, without the overhead of time details.
- Only 2 bytes per date - Uses
uint16internally with bit packing - 87.5% less memory than
time.Time(16 bytes) - Perfect for large datasets, databases, and memory-constrained environments
- Direct comparisons - Use standard operators (
>,<,==,>=,<=) - No boilerplate - Clean, intuitive API
- Automatic normalization - Invalid dates are automatically corrected
- 100% test coverage - Thoroughly tested and reliable
- Supported range: January 1, 2000 to December 31, 2127
- Dates outside this range are clamped to boundaries
- Optimized for modern applications
go get github.com/kaatinga/bublykpackage main
import (
"fmt"
"time"
"github.com/kaatinga/bublyk"
)
func main() {
// Get current date
today := bublyk.Now()
fmt.Println("Today:", today) // Output: 2025-11-16
// Create specific dates
date1 := bublyk.NewDate(2024, 12, 25)
date2 := bublyk.NewDate(2025, 1, 1)
// Direct comparisons
if date2 > date1 {
fmt.Println("New Year comes after Christmas")
}
// Convert from time.Time
t := time.Now().AddDate(0, 0, 5)
futureDate := bublyk.NewDateFromTime(&t)
fmt.Println("5 days from now:", futureDate)
}Returns the current date in UTC.
today := bublyk.Now()Creates a new date. Automatically normalizes invalid dates.
date := bublyk.NewDate(2024, 2, 29) // Leap day
normalized := bublyk.NewDate(2024, 13, 1) // Becomes 2025-01-01Converts a time.Time to a Date. Returns zero date if t is nil.
t := time.Now()
date := bublyk.NewDateFromTime(&t)Returns the first day of the current month.
monthStart := bublyk.CurrentMonth() // e.g., 2025-11-01Parses a date string in YYYY-MM-DD format.
date, err := bublyk.Parse("2024-12-25")
if err != nil {
log.Fatal(err)
}date := bublyk.NewDate(2024, 12, 25)
year := date.Year() // uint16: 2024
month := date.Month() // byte: 12
day := date.Day() // byte: 25// Check if date is set (non-zero)
if date.IsSet() {
fmt.Println("Date is valid")
}
// Check if date is in the future
if date.IsFuture() {
fmt.Println("This date hasn't happened yet")
}
// Month-level comparisons
date1 := bublyk.NewDate(2024, 6, 15)
date2 := bublyk.NewDate(2024, 3, 20)
if date1.MonthAfter(date2) {
fmt.Println("date1 is at least one month after date2")
}
if date2.MonthBefore(date1) {
fmt.Println("date2 is at least one month before date1")
}date := bublyk.NewDate(2024, 12, 25)
// Move by days
tomorrow := date.NextDay()
yesterday := date.PreviousDay()
// Move by weeks
nextWeek := date.NextWeek()
lastWeek := date.PreviousWeek()date := bublyk.NewDate(2024, 12, 25)
// Default format (YYYY-MM-DD)
str := date.String() // "2024-12-25"
// Custom format using time.Time layouts
formatted := date.Format(time.RFC822) // "25 Dec 24 00:00 UTC"
// European format (DD.MM.YYYY)
european := date.DMYWithDots() // "25.12.2024"date := bublyk.NewDate(2024, 12, 25)
// Convert to time.Time
t := date.Time() // *time.Time in UTCStore dates efficiently in databases:
type Event struct {
ID int
Name string
EventDate bublyk.Date // Only 2 bytes!
}startDate := bublyk.NewDate(2024, 1, 1)
endDate := bublyk.NewDate(2024, 12, 31)
for date := startDate; date <= endDate; date = date.NextDay() {
// Process each day of 2024
}// Check if subscription is expired
subscription := getSubscription()
if subscription.ExpiryDate < bublyk.Now() {
sendRenewalNotice()
}
// Calculate billing periods
billingDate := bublyk.NewDate(2024, 1, 1)
for i := 0; i < 12; i++ {
processBilling(billingDate)
billingDate = billingDate.NextMonth() // Custom logic
}The Date type uses bit packing to store year, month, and day in a single uint16:
Bits: [15-9: Year offset] [8-5: Month] [4-0: Day]
7 bits (0-127) 4 bits (0-15) 5 bits (0-31)
- Year: Stored as offset from 2000 (0-127 = years 2000-2127)
- Month: 4 bits (1-12)
- Day: 5 bits (1-31)
Invalid dates are automatically normalized using Go's time package:
bublyk.NewDate(2024, 2, 30) // → 2024-03-01
bublyk.NewDate(2024, 13, 1) // → 2025-01-01
bublyk.NewDate(2024, 4, 31) // → 2024-05-01
bublyk.NewDate(1999, 1, 1) // → 2000-01-01 (minimum)
bublyk.NewDate(2200, 1, 1) // → 2127-12-31 (maximum)- Comparisons: Direct integer comparison (extremely fast)
- Memory: 2 bytes vs 16 bytes for
time.Time - Parsing: Optimized with
faststrconvpackage - Navigation: Bit manipulation for same-month operations
- Date range: 2000-01-01 to 2127-12-31 only
- No time component: Only stores calendar dates
- UTC only: No timezone support (by design)
- No locale: Formatting is limited to standard layouts
Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests.
MIT License - see LICENSE file for details.