A Go package that provides a nullable time type with support for JSON, SQL databases, and optional BSON marshaling for MongoDB.
Timi uses Go workspaces to provide a clean, dependency-free core package while offering optional integrations and comprehensive testing.
import "github.com/ieshan/timi"
// β
Zero external dependencies added to your project!timi/
βββ go.work # Generated by ./dev.sh setup (gitignored)
βββ go.mod # Main package (zero dependencies)
βββ timi.go # Core nullable time functionality
βββ timi_unit_test.go # Unit tests (no external deps)
βββ dev.sh # Primary development script
βββ integration-tests/ # Database integration testing workspace
β βββ go.mod # Integration dependencies isolated here
β βββ timi_mongo_test.go # MongoDB integration tests
β βββ timi_gorm_test.go # SQL database tests (MariaDB, PostgreSQL, SQLite)
βββ mongodb/ # MongoDB utilities workspace
β βββ go.mod # MongoDB driver dependencies
β βββ bson_helpers.go # BSON marshaling utilities
β βββ bson_helpers_test.go # BSON helpers tests
βββ compose.yml # Docker services for integration testing
go get github.com/ieshan/timi- β JSON marshaling/unmarshaling
- β
SQL database support via
database/sql - β Time manipulation methods (After, Before, Add, etc.)
- β UTC timezone enforcement
- β Null value handling
- π§ MongoDB BSON support (dedicated workspace)
- π§ͺ Full database integration tests (MongoDB, MariaDB, PostgreSQL, SQLite)
package main
import (
"encoding/json"
"fmt"
"time"
"github.com/ieshan/timi"
)
func main() {
// Create times
now := timi.Now()
christmas := timi.Date(2024, time.December, 25, 15, 30, 0, 0, time.UTC)
nilTime := timi.NilTime
fmt.Printf("Current time: %s\n", now.String())
fmt.Printf("Christmas: %s\n", christmas.String())
fmt.Printf("Nil time is null: %t\n", nilTime.IsNull())
// JSON marshaling - works out of the box
type Event struct {
Name string `json:"name"`
StartTime timi.Time `json:"start_time"`
EndTime timi.Time `json:"end_time,omitempty"`
}
event := Event{
Name: "Holiday Party",
StartTime: christmas,
EndTime: nilTime, // Will be null in JSON
}
jsonData, _ := json.Marshal(event)
fmt.Printf("JSON: %s\n", jsonData)
// Output: {"name":"Holiday Party","start_time":"2024-12-25T15:30:00Z","end_time":null}
}import (
"database/sql"
"github.com/ieshan/timi"
_ "github.com/mattn/go-sqlite3"
)
func main() {
db, _ := sql.Open("sqlite3", ":memory:")
// Create table
db.Exec(`CREATE TABLE events (
id INTEGER PRIMARY KEY,
name TEXT,
start_time DATETIME,
end_time DATETIME
)`)
// Insert with timi.Time (handles NULL values automatically)
_, err := db.Exec(
"INSERT INTO events (name, start_time, end_time) VALUES (?, ?, ?)",
"Meeting", timi.Now(), timi.NilTime,
)
// Query back
var name string
var startTime, endTime timi.Time
err = db.QueryRow("SELECT name, start_time, end_time FROM events WHERE id = 1").
Scan(&name, &startTime, &endTime)
fmt.Printf("Event: %s, Start: %s, End null: %t\n",
name, startTime.String(), endTime.IsNull())
}The mongodb workspace provides ready-to-use BSON helpers:
// In your project, import the MongoDB driver:
// go get go.mongodb.org/mongo-driver/v2
// Copy the pattern from mongodb/bson_helpers.go:
import (
"github.com/ieshan/timi"
"github.com/ieshan/timi/mongodb"
"go.mongodb.org/mongo-driver/v2/bson"
)
// Option 1: Use helper functions directly
bType, data, err := mongodb.MarshalTimiBSON(timi.Now())
nullType, nullData, _ := mongodb.MarshalTimiBSON(timi.NilTime)
// Convert BSON back to timi.Time
t, err := mongodb.UnmarshalTimiBSON(bType, data)
// Option 2: Use TimiBSONWrapper (implements bson.ValueMarshaler/ValueUnmarshaler)
type Event struct {
ID bson.ObjectID `bson:"_id"`
Name string `bson:"name"`
StartTime mongodb.TimiBSONWrapper `bson:"start_time"`
EndTime mongodb.TimiBSONWrapper `bson:"end_time"`
}
event := Event{
ID: bson.NewObjectID(),
Name: "Meeting",
StartTime: mongodb.TimiBSONWrapper{Time: timi.Now()},
EndTime: mongodb.TimiBSONWrapper{Time: timi.NilTime},
}The dev.sh script is the primary tool for local development:
# Setup: initialize workspace, sync deps, tidy all modules
./dev.sh setup
# Run all tests with race detection across all modules
./dev.sh test
# Run short tests (skips integration tests)
./dev.sh test-short
# Full CI simulation (vet + build + test)
./dev.sh ci
# Other useful commands
./dev.sh build # Build all packages
./dev.sh vet # Run go vet in all modules
./dev.sh tidy # Tidy all go.mod files and sync workspace
./dev.sh clean # Clean caches and remove generated workspace files
./dev.sh help # Show all available commandsFor tests that require databases (MongoDB, PostgreSQL, MariaDB), use Docker:
# Run all tests via Docker Compose (includes databases)
./dev.sh test-docker
# Run only integration tests via Docker
./dev.sh test-integration-docker
# Run only MongoDB workspace tests via Docker
./dev.sh test-mongodb-dockerYou can also use docker compose directly:
# Bring up test services and run all tests
docker compose up --abort-on-container-exit
docker compose down| Service | Purpose |
|---|---|
test |
Runs all Go tests across all modules |
mongo |
MongoDB 8 for integration tests |
postgres |
PostgreSQL 18 for integration tests |
mariadb |
MariaDB 12 for integration tests |
If you prefer running Go commands directly:
# Unit tests (fast, no setup required)
go test -v
# Integration tests (requires manual database setup)
cd integration-tests && go test -v
# MongoDB workspace tests
cd mongodb && go test -v- JSON marshaling/unmarshaling
- Time creation and manipulation
- String representation
- Null value handling
-
SQL Databases (
timi_gorm_test.go):- MariaDB with microsecond precision
- PostgreSQL with timezone support
- SQLite with nanosecond precision
- CRUD operations, queries, edge cases
-
MongoDB (
timi_mongo_test.go):- BSON marshaling/unmarshaling
- MongoDB queries and operations
- Precision handling (milliseconds)
- Edge cases and timezone handling
- BSON helper function testing
- Round-trip conversion validation
- Null value handling in BSON context
This project uses Go workspaces to solve the dependency management problem:
# Traditional approach - forces dependencies on all users
go mod tidy # β Adds MongoDB, GORM, database drivers to main go.mod# Workspace approach - clean separation
go mod tidy # β
Main package: zero dependencies
cd integration-tests && go mod tidy # β
Heavy deps isolated here
cd mongodb && go mod tidy # β
MongoDB deps isolated herego 1.26
use (
. # Main timi package
./integration-tests # Integration tests module
./mongodb # MongoDB utilities module
)- β Zero forced dependencies: Main package stays clean
- β Complete testing: Full database integration coverage
- β Developer friendly: Work on all modules simultaneously
- β CI/CD ready: Test modules independently
- β Optional features: Use MongoDB helpers when needed
| Module | Dependencies | Purpose |
|---|---|---|
| Main Package | go.mod β Zero external deps |
Core time functionality |
| Integration Tests | go.mod β MongoDB, GORM, DB drivers |
Comprehensive database testing |
| MongoDB Workspace | go.mod β MongoDB driver only |
BSON utilities and tests |
| Your Project | Only what you choose | Clean imports |
import "github.com/ieshan/timi"
// β
No external dependencies added to your go.mod
// β
JSON, SQL, time operations work perfectly# You explicitly add MongoDB to YOUR project
go get go.mongodb.org/mongo-driver/v2
# Import the mongodb submodule for BSON helpers
# import "github.com/ieshan/timi/mongodb"
# β
You control your dependencies# Clone repository
git clone https://github.com/ieshan/timi
cd timi
# Setup and test everything locally
./dev.sh setup # Initialize workspace
./dev.sh test # Run all tests
./dev.sh ci # Full CI simulation
# Or work directly with Go commands
go work sync
go test -v
cd integration-tests && go test -v
cd ../mongodb && go test -v# Initial setup
./dev.sh setup
# Quick development cycle
./dev.sh test-short # Fast tests (skips integration)
./dev.sh test # All tests with race detection
./dev.sh ci # Full CI simulation
# Workspace management
./dev.sh tidy # Tidy modules and sync workspace
./dev.sh clean # Clean caches and generated files# Run tests that need databases (MongoDB, PostgreSQL, MariaDB)
./dev.sh test-docker
# Targeted Docker testing
./dev.sh test-integration-docker # Integration tests only
./dev.sh test-mongodb-docker # MongoDB workspace only
# Or use docker compose directly
docker compose up --abort-on-container-exit
docker compose downtype Time sql.NullTime
var NilTime = Time{Time: time.Time{}, Valid: false}func Now() Time
func Date(year int, month time.Month, day, hour, min, sec, nsec int, loc *time.Location) Timefunc (t Time) String() string
func (t *Time) IsNull() bool
func (t Time) IsZero() bool
func (t Time) Zone() (string, int)
// Comparison
func (t Time) After(u Time) bool
func (t Time) Before(u Time) bool
func (t Time) Compare(u Time) int
func (t Time) Equal(u Time) bool
// Time components
func (t Time) Date() (year int, month time.Month, day int)
func (t Time) Year() int
func (t Time) Month() time.Month
func (t Time) Day() int
func (t Time) Weekday() time.Weekday
func (t Time) ISOWeek() (year, week int)
func (t Time) Clock() (hour, min, sec int)
func (t Time) Hour() int
func (t Time) Minute() int
func (t Time) Second() int
func (t Time) Nanosecond() int
func (t Time) YearDay() int
// Arithmetic
func (t Time) Add(d time.Duration) Time
func (t Time) Sub(u Time) time.Duration
func (t Time) AddDate(years int, months int, days int) Time
func (t Time) Truncate(d time.Duration) Time
func (t Time) Round(d time.Duration) Time
// Unix timestamps
func (t Time) Unix() int64
func (t Time) UnixMilli() int64
func (t Time) UnixMicro() int64
func (t Time) UnixNano() int64
// JSON support
func (t Time) MarshalJSON() ([]byte, error)
func (t *Time) UnmarshalJSON(data []byte) error
func (t Time) MarshalText() ([]byte, error)
func (t *Time) UnmarshalText(data []byte) error
// SQL support
func (t *Time) Scan(value interface{}) error
func (t Time) Value() (driver.Value, error)func MarshalTimiBSON(t timi.Time) (bson.Type, []byte, error)
func UnmarshalTimiBSON(bType bson.Type, data []byte) (timi.Time, error)
type TimiBSONWrapper struct {
timi.Time
}
// Implements bson.ValueMarshaler and bson.ValueUnmarshaler- Setup: Run
./dev.sh setupto initialize the workspace - Core changes: Work in main directory, test with
./dev.sh test-short - Integration changes: Work in
integration-tests/, test with./dev.sh test-integration-docker - MongoDB changes: Work in
mongodb/, test with./dev.sh test-mongodb-docker - Before submitting: Always run
./dev.sh cito simulate the full CI pipeline
Use ./dev.sh for day-to-day development. Use Docker for integration tests that require databases.
MIT
π Timi provides clean, nullable time handling with zero dependency pollution - the best of both worlds!