Simple Date Handling in Go

Fork me on GitHubGo Month Package
Package time provides functionality focused on managing times and dates. The package consists of a wide range of features designed to facilitate a Gregorian calendar. Here is a canonical example:

package main

import (
	"fmt"
	"time"
)

func main() {
	t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
	fmt.Printf("Time is set to %s\n", t.Local())
}

Last Day of the Month

Very nice, neat, and concise. Let’s say that I want to calculate the last day of the month, for any given month in any given year. For example, I know that 2012 was a leap year, and February therefore had 29 days. Here is how such a calculation would look in C#:

var lastDayofMonth = DateTime.DaysInMonth(2012, 02);

And in Java:

// Java 7
SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy");
Date convertedDate = dateFormat.parse(date);
Calendar c = Calendar.getInstance();
c.setTime(convertedDate);
c.set(Calendar.DAY_OF_MONTH, c.getActualMaximum(Calendar.DAY_OF_MONTH));

// Java 8
LocalDate date = LocalDate.of(2012, Month.FEBRUARY, 1);

System.out.printf("The last day of February is: %s%n",
	date.with(TemporalAdjusters.lastDayOfMonth()));

That’s a little verbose. Let’s try JavaScript:

return new Date(2012, 2, 0).getDate();

How does Go stack up against these idioms?

m := time.February
t := time.Date(2012, m+1, 0, 0, 0, 0, 0, time.UTC)
fmt.Println(2012, m, t.Day())

That’s quite succinct, but not very intuitive. Essentially we’re incrementing the month value by 1, and setting the day value to zero. This causes the underlying date mechanisms to roll back the date to the last day of the previous month – remember, we specified the month value as February, and incremented this value by 1. Now our month value is equal to March. Setting the day value to 0 effectively rolls the date back to the last day of February; in this case, the 29th.

This method gets the job done, at the expense of adding verbosity. The above example in C# is arguably the most intuitive. It would be nice if we could calculate the last day of the month in a similar manner in Go.

Fortunately the Go Month package provides this functionality. The package is designed with developer productivity in mind, encapsulating some of the more verbose features of underlying Go packages, and exposing them intuitively. Let’s install the package and take a look at some examples:

go get github.com/daishisystems/month
    // The last numeric day of January is 31
    m := month.January
    fmt.Printf("The last numeric day of %s is %d\n", m, m.LastDay(2015))

    // The last numeric day of February is 28
    m = month.February
    fmt.Printf("The last numeric day of %s is %d\n", m, month.February.LastDay(2015))

    // The last numeric day of February is 29
    m = month.February
    fmt.Printf("The last numeric day of %s is %d\n", m, m.LastDay(2008))

    // The last numeric day of July is 31
    m = month.July
    fmt.Printf("The last numeric day of %s is %d\n", m, m.LastDay(2015))

What exactly does LastDay do? Is it just a wrapper that encapsulates built-in Go functionality?

No, it’s a bespoke method that doesn’t rely on existing date or time functionality. In fact, the package does not depend on any other packages at all. Here is the method in detail:

func (m Month) LastDay(year uint16) int {

	if m == 2 {

		isLeapYear := year%4 == 0 &&
			(year%100 != 0 || year%400 == 0)

		if isLeapYear {
			return 29
		}
		return 28
	}
	return monthDays[int(m)]
}

The method accepts a year value. This is necessary, given that the month of February contains 29 days in a leap year. Notice that I refer to the code snippet as a method. Methods and functions are not interchangeable terms in Go. Specifically, a method must have a receiver; a type to which the method is applied. In this case, our receiver is of type Month. Think of Go methods as being similar to class-level functions in C# or Java.

The package contains a static collection of months represented as integers, and the corresponding number of days in each month. Only February is omitted:

var monthNames = [12]string{
	"January",
	"February",
	"March",
	"April",
	"May",
	"June",
	"July",
	"August",
	"September",
	"October",
	"November",
	"December",
}

LastDay simply returns the value of the above map (Dictionary in C#, HashMap in Java) associated with the specified month, if the month is any other month than February. Otherwise, the method determines whether or not the year is a leap year. A leap year is defined as being:

  • Evenly divisible by 4
  • Not evenly divisible by 100, unless it is also evenly divisible by 400

LastDay employs this calculation as follows:

isLeapYear := year%4 == 0 &&
    (year%100 != 0 || year%400 == 0)

Very simply, if the year is determined to be a leap year, LastDay returns 29, otherwise, 28:

if isLeapYear {
    return 29
}
return 28

Summary

The Go Month Package provides functionality in an intuitive format, has no dependencies on existing date or time packages (or any packages at all), and very little verbosity. Please reach out and contact me for questions, suggestions, or to just talk tech in general.

Connect with me:

RSSGitHubTwitter
LinkedInYouTubeGoogle+

2 thoughts on “Simple Date Handling in Go

Leave a comment