Awesome Go

go-date-fns

CategoryDate and Time
SubcategoryDate and Time
Stars6

A comprehensive date utility library for Go, inspired by date-fns, with 140+ pure and immutable functions

About go-date-fns

This large README is shown as a plain-text preview. Read the full README at the source

# ๐Ÿ—“๏ธ go-date-fns

> A modern, functional date utility library for Go, inspired by the beloved date-fns JavaScript library.

[![Go Version](https://img.shields.io/badge/Go-%3E%3D%201.21-007d9c.svg)](https://golang.org/)
[![License: LGPL v2.1](https://img.shields.io/badge/License-LGPL_v2.1-blue.svg)](https://www.gnu.org/licenses/lgpl-2.1)
[![Go Report Card](https://goreportcard.com/badge/github.com/chmenegatti/go-date-fns)](https://goreportcard.com/report/github.com/chmenegatti/go-date-fns)
[![CI](https://github.com/chmenegatti/go-date-fns/actions/workflows/ci.yml/badge.svg)](https://github.com/chmenegatti/go-date-fns/actions/workflows/ci.yml)
[![Go Reference](https://pkg.go.dev/badge/github.com/chmenegatti/go-date-fns.svg)](https://pkg.go.dev/github.com/chmenegatti/go-date-fns)
[![Coverage](https://codecov.io/gh/chmenegatti/go-date-fns/branch/main/graph/badge.svg)](https://app.codecov.io/gh/chmenegatti/go-date-fns)


**go-date-fns** brings the power and elegance of functional date manipulation to Go. With over **150+ pure, immutable functions**, it provides a comprehensive toolkit for working with dates and times in a safe, predictable, and timezone-aware manner.

---

## โœจ Why go-date-fns?

In the world of Go development, working with dates can be complex and error-prone. **go-date-fns** solves this by providing:

- ๐Ÿงฎ **Pure Functions**: No side effects, original dates never modified
- ๐ŸŒ **Timezone Aware**: Full timezone support across all operations  
- ๐Ÿš€ **Zero Dependencies**: Only uses Go standard library
- ๐ŸŽฏ **Type Safe**: Leverages Go's strong typing system
- ๐Ÿ“ฆ **WebAssembly Ready**: Compile to WASM without issues
- ๐Ÿงช **Battle Tested**: 100% test coverage with comprehensive benchmarks
- ๐Ÿ“š **Well Documented**: Every function has clear examples and documentation

---

## ๐Ÿš€ Quick Start

### Installation

```bash
go get github.com/chmenegatti/go-date-fns
```

### Basic Usage

```go
package main

import (
    "fmt"
    "time"
    
    "github.com/chmenegatti/go-date-fns/dateutils"
)

func main() {
    // Parse a date
    date, _ := dateutils.Parse("2024-01-15", time.UTC)
    
    // Add 7 days
    nextWeek := dateutils.AddDays(date, 7)
    
    // Format the result
    formatted, _ := dateutils.Format(nextWeek, dateutils.DateISO, nil)
    fmt.Println(formatted) // Output: 2024-01-22
    
    // Check if it's a weekend
    isWeekend := dateutils.IsWeekend(nextWeek)
    fmt.Printf("Is weekend: %t\n", isWeekend)
}
```

---

## ๐Ÿ“š Complete Feature Overview

### ๐Ÿ” **Parsing Functions**

Convert strings to time.Time objects with intelligent format detection:

```go
// Smart parsing with common formats
date, err := dateutils.Parse("2024-01-15", time.UTC)
date, err := dateutils.Parse("01/15/2024", time.UTC)
date, err := dateutils.Parse("January 15, 2024", time.UTC)

// ISO 8601 parsing
isoDate, err := dateutils.ParseISO("2024-01-15T14:30:00Z", time.UTC)

// Custom format parsing
customDate, err := dateutils.ParseWithFormat("15-01-2024", "02-01-2006", time.UTC)

// Validation
isValid := dateutils.IsValidISO("2024-01-15T14:30:00Z")
```

### ๐ŸŽจ **Formatting Functions**

Transform dates into human-readable strings:

```go
date := time.Date(2024, 1, 15, 14, 30, 0, 0, time.UTC)

// Predefined formats
iso, _ := dateutils.Format(date, dateutils.DateISO, nil)           // "2024-01-15"
us, _ := dateutils.Format(date, dateutils.DateUS, nil)             // "01/15/2024"
readable, _ := dateutils.Format(date, dateutils.Readable, nil)     // "January 15, 2024"
dateTime, _ := dateutils.Format(date, dateutils.DateTime24, nil)   // "2024-01-15 14:30:00"

// Custom formatting with user-friendly placeholders
custom, _ := dateutils.FormatCustom(date, "DD/MM/YYYY at HH:mm", nil)  // "15/01/2024 at 14:30"
custom, _ := dateutils.FormatCustom(date, "MMMM DD, YYYY", nil)        // "January 15, 2024"

// Safe formatting (returns empty string on error)
safe := dateutils.FormatSafe(date, dateutils.DateISO, nil)

// Formatting with fallback
withDefault := dateutils.FormatWithDefault(date, dateutils.DateISO, nil, "N/A")
```

### โš–๏ธ **Comparison Functions**

Compare dates with precision and timezone awareness:

```go
date1 := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
date2 := time.Date(2024, 1, 16, 15, 0, 0, 0, time.UTC)

// Precise comparisons (includes time)
dateutils.IsBefore(date1, date2)        // true
dateutils.IsAfter(date1, date2)         // false
dateutils.IsEqual(date1, date1)         // true
dateutils.IsBeforeOrEqual(date1, date1) // true

// Date-only comparisons (ignores time)
dateutils.IsBeforeDate(date1, date2)    // true
dateutils.IsAfterDate(date1, date2)     // false

// Same period comparisons
dateutils.IsSameDay(date1, date2)       // false (different days)
dateutils.IsSameWeek(date1, date2)      // true (same week)
dateutils.IsSameMonth(date1, date2)     // true (same month)
dateutils.IsSameYear(date1, date2)      // true (same year)
dateutils.IsSameHour(date1, date2)      // false (different hours)
dateutils.IsSameMinute(date1, date2)    // false (different minutes)

// Sorting and selection
times := []time.Time{date1, date2, time.Now()}
earliest := dateutils.Min(times)        // Get earliest time
latest := dateutils.Max(times)          // Get latest time
closest := dateutils.ClosestTo(date1, times) // Get closest to date1

// Comparison with ordering
result := dateutils.CompareAsc(date1, date2)  // -1 (date1 before date2)
result = dateutils.CompareDesc(date1, date2)  // 1 (desc order)

// Timezone-aware comparisons
est, _ := time.LoadLocation("America/New_York")
dateutils.IsBeforeInTimezone(date1, date2, est)
```

### ๐Ÿ”ง **Manipulation Functions**

Add, subtract, and modify dates immutably:

```go
baseDate := time.Date(2024, 1, 15, 0, 0, 0, 0, time.UTC) // Monday

// Add/subtract days, weeks, months, years
nextWeek := dateutils.AddDays(baseDate, 7)         // 2024-01-22
lastWeek := dateutils.SubDays(baseDate, 7)         // 2024-01-08
nextMonth := dateutils.AddMonths(baseDate, 1)      // 2024-02-15
lastYear := dateutils.SubYears(baseDate, 1)        // 2023-01-15

// Add/subtract time units
laterToday := dateutils.AddHours(baseDate, 5)      // Add 5 hours
earlierToday := dateutils.SubMinutes(baseDate, 30) // Subtract 30 minutes
futureTime := dateutils.AddSeconds(baseDate, 120)  // Add 2 minutes

// Business days (skips weekends)
nextBusinessDay := dateutils.AddBusinessDays(baseDate, 5)  // Skips weekend, lands on weekday
pastBusinessDay := dateutils.SubBusinessDays(baseDate, 3)  // Goes back 3 business days

// Timezone-aware addition
est, _ := time.LoadLocation("America/New_York")
dateInEST := dateutils.AddDaysWithTimezone(baseDate, 1, est)
```

### ๐Ÿ“Š **Get Functions**

Extract specific components from dates:

```go
date := time.Date(2024, 6, 15, 14, 30, 45, 500000000, time.UTC) // Saturday

// Date components
year := dateutils.GetYear(date)           // 2024
month := dateutils.GetMonth(date)         // 6 (June)
day := dateutils.GetDate(date)            // 15
dayOfWeek := dateutils.GetDay(date)       // 6 (Saturday, 0=Sunday)
dayOfYear := dateutils.GetDayOfYear(date) // 167
quarter := dateutils.GetQuarter(date)     // 2 (Q2)

// Time components
hours := dateutils.GetHours(date)         // 14
minutes := dateutils.GetMinutes(date)     // 30
seconds := dateutils.GetSeconds(date)     // 45
milliseconds := dateutils.GetMilliseconds(date) // 500

// Week-related
week := dateutils.GetWeek(date)           // ISO week number
weekOfMonth := dateutils.GetWeekOfMonth(date) // Week of the month
weekYear := dateutils.GetWeekYear(date)   // ISO week year
```

### โš™๏ธ **Set Functions**

Create new dates by setting specific components:

```go
baseDate := time.Date(2024, 6, 15, 14, 30, 45, 0, time.UTC)

// Set date components
newYear := dateutils.SetYear(baseDate, 2025)      // 2025-06-15 14:30:45
newMonth := dateutils.SetMonth(baseDate, 12)      // 2024-12-15 14:30:45
newDay := dateutils.SetDate(baseDate, 1)          // 2024-06-01 14:30:45
newQuarter := dateutils.SetQuarter(baseDate, 1)   // 2024-01-15 14:30:45

// Set time components
newHours := dateutils.SetHours(baseDate, 9)       // 2024-06-15 09:30:45
newMinutes := dateutils.SetMinutes(baseDate, 0)   // 2024-06-15 14:00:45
newSeconds := dateutils.SetSeconds(baseDate, 0)   // 2024-06-15 14:30:00

// Set by day of week or day of year
monday := dateutils.SetDay(baseDate, 1)           // Set to Monday of the same week
newYearDay := dateutils.SetDayOfYear(baseDate, 100) // Set to 100th day of year
```

### ๐Ÿ“ **Difference Functions**

Calculate time differences with various precision levels:

```go
start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2024, 1, 15, 12, 30, 45, 0, time.UTC)

// Time unit differences (integer)
years := dateutils.DifferenceInYears(end, start)                  // 0
months := dateutils.DifferenceInMonths(end, start)                // 0
weeks := dateutils.DifferenceInWeeks(end, start)                  // 2
days := dateutils.DifferenceInDays(end, start)                    // 14
hours := dateutils.DifferenceInHours(end, start)                  // 348
minutes := dateutils.DifferenceInMinutes(end, start)              // 20910
seconds := dateutils.DifferenceInSeconds(end, start)              // 1254645

// Calendar-based differences (ignore time components)
calendarYears := dateutils.DifferenceInCalendarYears(end, start)  // 0
calendarMonths := dateutils.DifferenceInCalendarMonths(end, start)// 0
calendarDays := dateutils.DifferenceInCalendarDays(end, start)    // 14
quarters := dateutils.DifferenceInQuarters(end, start)            // 0
calendarQuarters := dateutils.DifferenceInCalendarQuarters(end, start) // 0

// Business and specialized differences
businessDays := dateutils.DifferenceInBusinessDays(end, start)    // 10 (excluding weekends)

// Floating-point precision
daysPrecise := dateutils.DifferenceInDaysFloat(end, start)        // 14.52...
weeksPrecise := dateutils.DifferenceInWeeksFloat(end, start)      // 2.07...
hoursPrecise := dateutils.DifferenceInHoursFloat(end, start)      // 348.51...

// Absolute differences (always positive)
absDays := dateutils.AbsDifferenceInDays(start, end)              // 14
absHours := dateutils.AbsDifferenceInHours(start, end)            // 348
absMinutes := dateutils.AbsDifferenceInMinutes(start, end)        // 20910
absMonths := dateutils.AbsDifferenceInMonths(start, end)          // 0
```

### โœ… **Validation Functions**

Comprehensive date validation and checking:

```go
date := time.Date(2024, 2, 29, 0, 0, 0, 0, time.UTC) // Leap year date
today := time.Now()

// Basic validation
dateutils.IsValid(date)                    // true
dateutils.IsValid(time.Time{})             // false (zero time)

// Year validation
dateutils.IsLeapYear(2024)                 // true
dateutils.IsLeapYear(2023)                 // false

// Day type validation
dateutils.IsWeekend(date)                  // true/false based on day
dateutils.IsWeekday(date)                  // opposite of IsWeekend

// Time-based validation
dateutils.IsToday(today)                   // true
dateutils.IsTomorrow(dateutils.AddDays(today, 1))   // true
dateutils.IsYesterday(dateutils.AddDays(today, -1)) // true

// Interval validation
start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
end := time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC)
test := time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC)
dateutils.IsWithinInterval(test, start, end)        // true

// Boundary validation
dateutils.IsFirstDayOfMonth(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC))  // true
dateutils.IsLastDayOfMonth(time.Date(2024, 1, 31, 0, 0, 0, 0, time.UTC))  // true
dateutils.IsFirstDayOfYear(time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC))   // true
dateutils.IsLastDayOfYear(time.Date(2024, 12, 31, 0, 0, 0, 0, time.UTC))  // true

// Comparison validation
date1 := time.Date(2024, 1, 15, 10, 0, 0, 0, time.UTC)
date2 := time.Date(2024, 1, 15, 15, 0, 0, 0, time.UTC)
dateutils.IsSameDate(date1, date2)         // true (same date, different time)
dateutils.IsSameMonth(date1, date2)        // true
dateutils.IsSameYear(date1, date2)         // true
```

### ๐Ÿ“… **Period Utility Functions**

Get the start and end of various time periods:

```go
date := time.Date(2024, 1, 15, 14, 30, 45, 0, time.UTC) // Monday, mid-day

// Day boundaries
startOfDay := dateutils.StartOfDay(date)    // 2024-01-15 00:00:00
endOfDay := dateutils.EndOfDay(date)        // 2024-01-15 23:59:59

// Week boundaries (Monday-based)
startOfWeek := dateutils.StartOfWeek(date)  // Previous Monday 00:00:00
endOfWeek := dateutils.EndOfWeek(date)      // Next Sunday 23:59:59

// Week boundaries (Sunday-based)
startOfWeekSunday := dateutils.StartOfWeekSunday(date)  // Previous Sunday 00:00:00
endOfWeekSaturday := dateutils.EndOfWeekSaturday(date)  // Next Saturday 23:59:59

// Month boundaries
startOfMonth := dateutils.StartOfMonth(date) // 2024-01-01 00:00:00
endOfMonth := dateutils.EndOfMonth(date)     // 2024-01-31 23:59:59

// Year boundaries
startOfYear := dateutils.StartOfYear(date)   // 2024-01-01 00:00:00
endOfYear := dateutils.EndOfYear(date)       // 2024-12-31 23:59:59

// Hour boundaries
startOfHour := dateutils.StartOfHour(date)   // 2024-01-15 14:00:00
endOfHour := dateutils.EndOfHour(date)       // 2024-01-15 14:59:59

// Minute boundaries
startOfMinute := dateutils.StartOfMinute(date) // 2024-01-15 14:30:00
endOfMinute := dateutils.EndOfMinute(date)     // 2024-01-15 14:30:59
```

---

### ๐Ÿ”„ **Interval Utilities**

**NEW:** Generate arrays of dates within intervals - perfect for reporting, scheduling, and data analysis:

```go
// Define an interval for Q1 2024
q1Interval := dateutils.Interval{
    Start: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
    End:   time.Date(2024, 3, 31, 23, 59, 59, 0, time.UTC),
}

// Get all months in Q1
months := dateutils.EachMonthOfInterval(q1Interval)
// Returns: [2024-01-01, 2024-02-01, 2024-03-01]

// Get all weekends in January 2024
january := dateutils.Interval{
    Start: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
    End:   time.Date(2024, 1, 31, 23, 59, 59, 0, time.UTC),
}
weekends := dateutils.EachWeekendOfInterval(january)
// Returns all Saturday and Sunday dates in January

// Get business days for a work week
workWeek := dateutils.Interval{
    Start: time.Date(2024, 1, 8, 0, 0, 0, 0, time.UTC),  // Monday
    End:   time.Date(2024, 1, 12, 23, 59, 59, 0, time.UTC), // Friday
}
businessDays := dateutils.EachBusinessDayOfInterval(workWeek)
// Returns: [Mon, Tue, Wed, Thu, Fri] (5 business days)

// Get all quarters in 2024
year := dateutils.Interval{
    Start: time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC),
    End:   time.Date(2024, 12, 31, 23, 59, 59, 0, time.UTC),
}
quarters := dateutils.EachQuarterOfInterval(year)
// Returns: [Q1, Q2, Q3, Q4] start dates

// Get hourly intervals for a workday
workday := dateutils.Interval{
    Start: time.Date(2024, 1, 15, 9, 0, 0, 0, time.UTC),
    End:   time.Date(2024, 1, 15, 17, 0, 0, 0, time.UTC),
}
hours := dateutils.EachHourOfInterval(workday)
// Returns: [09:00, 10:00, 11:00, ..., 17:00]
```

**Available Each Functions:**
- `EachDayOfInterval` - All days in interval
- `EachWeekOfInterval` - Weekly intervals (Monday start)
- `EachWeekOfIntervalSunday` - Weekly intervals (Sunday start)
- `EachMonthOfInterval` - Monthly intervals
- `EachYearOfInterval` - Yearly intervals  
- `EachQuarterOfInterval` - Quarterly intervals
- `EachHourOfInterval` - Hourly intervals
- `EachMinuteOfInterval` - Minute intervals
- `EachWeekendOfInterval` - Weekend days only
- `EachBusinessDayOfInterval` - Business days only

---

## ๐ŸŒ Timezone Support

**go-dateutils** provides first-class timezone support across all functions:

```go
// Load timezones
utc := time.UTC
est, _ := time.LoadLocation("America/New_York")
pst, _ := time.LoadLocation("America/Los_Angeles")
tokyo, _ := time.LoadLocation("Asia/Tokyo")

// Parse with timezone
date, _ := dateutils.ParseISO("2024-01-15T10:00:00Z", est)

// Format in different timezones
utcFormat, _ := dateutils.Format(date, dateutils.DateTime24, utc)
estFormat, _ := dateutils.Format(date, dateutils.DateTime24, est)
pstFormat, _ := dateutils.Format(date, dateutils.DateTime24, pst)

// Timezone-aware comparisons
isAfterInEST := dateutils.IsAfterInTimezone(date1, date2, est)

// Add days with timezone conversion
futureInTokyo := dateutils.AddDaysWithTimezone(date, 7, tokyo)
```

---

## ๐ŸŽฏ Real-World Examples

### ๐Ÿ“Š Business Date Calculations

```go
// Calculate project deadline (20 business days from today)
today := time.Now()
deadline := dateutils.AddBusinessDays(today, 20)
fmt.Printf("Project deadline: %s\n", 
    dateutils.FormatSafe(deadline, dateutils.Readable, nil))

// Calculate working days between two dates
startDate, _ := dateutils.Parse("2024-01-01", time.UTC)
endDate, _ := dateutils.Parse("2024-01-31", time.UTC)
workingDays := dateutils.DifferenceInBusinessDays(endDate, startDate)
fmt.Printf("Working days in January 2024: %d\n", workingDays)

// Set specific meeting times using Set functions
meetingBase := time.Date(2024, 6, 15, 0, 0, 0, 0, time.UTC)
quarterlyMeeting := dateutils.SetQuarter(meetingBase, 3)      // Q3 meeting
monthlyMeeting := dateutils.SetDate(quarterlyMeeting, 1)      // First of the month
finalTime := dateutils.SetHours(monthlyMeeting, 14)          // 2 PM meeting

// Get components for reporting
quarter := dateutils.GetQuarter(finalTime)                   // Which quarter?
week := dateutils.GetWeek(finalTime)                         // Which week of year?
dayOfYear := dateutils.GetDayOfYear(finalTime)              // Day number in year

// Calculate time spans using new time unit functions
projectStart := dateutils.SubMonths(today, 3)                // 3 months ago
deadline2 := dateutils.AddHours(projectStart, 8*24*30)       // Add work hours
timeLeft := dateutils.SubDays(deadline2, 5)                  // 5 days before deadline
```

### ๐Ÿ“… Event Planning

```go
// Plan a weekly meeting series
meetingStart, _ := dateutils.ParseISO("2024-01-15T14:00:00Z", time.UTC)
meetings := make([]time.Time, 0, 12)

for i := 0; i < 12; i++ {
    meeting := dateutils.AddWeeks(meetingStart, i)
    
    // Skip if it falls on a weekend
    if dateutils.IsWeekend(meeting) {
        meeting = dateutils.AddDays(meeting, 1) // Move to Monday
    }
    
    meetings = append(meetings, meeting)
}

// Display the meeting schedule
for i, meeting := range meetings {
    formatted, _ := dateutils.FormatCustom(meeting, "MMMM DD, YYYY at HH:mm", nil)
    fmt.Printf("Meeting %d: %s\n", i+1, formatted)
}
```

### ๐Ÿ”ง Date Component Manipulation

```go
// Extract and manipulate date components
baseDate := time.Date(2024, 8, 15, 16, 45, 30, 0, time.UTC)

// Extract all components
fmt.Printf("Year: %d, Quarter: Q%d\n", dateutils.GetYear(baseDate), dateutils.GetQuarter(baseDate))
fmt.Printf("Month: %d, Day: %d, Day of Year: %d\n", 
    dateutils.GetMonth(baseDate), dateutils.GetDate(baseDate), dateutils.GetDayOfYear(baseDate))
fmt.Printf("Week: %d, Day of Week: %d\n", dateutils.GetWeek(baseDate), dateutils.GetDay(baseDate))
fmt.Printf("Time: %02d:%02d:%02d.%03d\n", 
    dateutils.GetHours(baseDate), dateutils.GetMinutes(baseDate), 
    dateutils.GetSeconds(baseDate), dateutils.GetMilliseconds(baseDate))

// Create variations by setting components
yearEnd := dateutils.SetMonth(dateutils.SetDate(baseDate, 31), 12)  // Dec 31st
startOfQuarter := dateutils.SetQuarter(baseDate, 1)                 // Q1 start
mondayMeeting := dateutils.SetDay(baseDate, 1)                      // Next Monday
nineAM := dateutils.SetHours(dateutils.SetMinutes(baseDate, 0), 9)  // 9:00 AM

// Time comparisons using new functions
date1 := time.Date(2024, 6, 15, 14, 30, 0, 0, time.UTC)
date2 := time.Date(2024, 6, 22, 10, 15, 0, 0, time.UTC)

if dateutils.IsSameWeek(date1, date2) {
    fmt.Println("Same week!")
} else if dateutils.IsSameMonth(date1, date2) {
    fmt.Println("Same month, different week")
}

// Find closest date from a set

Frequently Asked Questions

What is go-date-fns?

go-date-fns is a Date and Time library for the Go programming language. A comprehensive date utility library for Go, inspired by date-fns, with 140+ pure and immutable functions

How do I install go-date-fns?

Install go-date-fns with the Go module system using `go get chmenegatti/go-date-fns`. Check the repository for the current installation instructions.

What category does go-date-fns belong to?

go-date-fns is listed under Date and Time, specifically Date and Time.

โ† Back to Date and Time