Hugo dateFormat

Understanding times and dates in Hugo templates is not as easy as it looks like. In this article we will have a deeper look how to deal with dateFormat to receive an output you expect.

What the documentation says

Converts the textual representation of the datetime into the other form or returns it of Go time.Time type value. These are formatted with the layout string.

{{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }} → “Wednesday, Jan 21, 2015”

Seems pretty straight forward, dateFormat is the function to call, first parameter is the format and the second parameter is the actual date. However, why Monday? why Jan? Why a 2 and not a 1 for the date? Why no ISO8601 as in the front matters.

These are the questions I still ask myself when I have to use dateFormat in templates. Unfortunately I cannot give you all of the answers below, but at least put some examples with the output.

Deeper look at date input

In the example from the documentation, the inputted date as a second parameter was formatted like YYYY-MM-DD (2015-01-21). But what do other formats return?

ISO8601

{{ dateFormat "Monday, Jan 2, 2006" "2015-01-21T20:54:45.847Z" }} → “Wednesday, Jan 21, 2015”

YYYY-MM-DD

{{ dateFormat "Monday, Jan 2, 2006" "2015-01-21" }} → “Wednesday, Jan 21, 2015”

RFC822

{{ dateFormat "Monday, Jan 2, 2006" "21 Jan 06 15:04 MST" }} → “Wednesday, Jan 21, 2015”

RFC822Z

{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "21 Jan 06 15:04 -0700" }} → “Wednesday, Jan 21, 2015”

RFC1123

{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Mon, 21 Jan 2006 15:04:05 MST" }} →  “Wednesday, Jan 21, 2015”

RFC339

{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Mon, 21 Jan 2006 15:04:05 -0700" }} → “Wednesday, Jan 21, 2015”

I’ve also tried the following formats:

{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Mon Jan _2 15:04:05 2006" }} → “ANSIC: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Mon Jan _2 15:04:05 MST 2006" }} → “UnixDate: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Mon Jan 02 15:04:05 -0700 2006" }} → “RubyDate: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Monday, 02-Jan-06 15:04:05 MST" }} → “RFC850: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "2006-01-02T15:04:05.999999999Z07:00" }} → “RFC339Nano: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "3:04PM" }} → “Kitchen: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Jan _2 15:04:05" }} → “Stamp: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Jan _2 15:04:05.000" }} → “StampMilli: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Jan _2 15:04:05.000000" }} → “StampMicro: Error.”
{{ dateFormat "Mon Jan 2 15:04:05 MST 2006" "Jan _2 15:04:05.000000000" }} → “StampNano: Error.”

So I have learned to always pass in a full ISO8601 or YYYY-MM-DD formatted string.

Weekdays

For the sake of ease all examples below using the ISO8601 type of input format. Lets checkout what we can get from the weekday.

Monday vs Tuesday

Ok, why do we have to work with Monday, and not Tuesday or Wednesday or whatever day of the week.

So, let’s test them all out.

{{ dateFormat "Monday" "2015-01-21T20:54:45.847Z" }} → “Wednesday”

{{ dateFormat "Tuesday" "2015-01-21T20:54:45.847Z" }} → “Tuesday”
{{ dateFormat "Wednesday" "2015-01-21T20:54:45.847Z" }} → “Wednesday”
{{ dateFormat "Thursday" "2015-01-21T20:54:45.847Z" }} → “Thursday”
{{ dateFormat "Friday" "2015-01-21T20:54:45.847Z" }} → “Friday”
{{ dateFormat "Saturday" "2015-01-21T20:54:45.847Z" }} → “Saturday”
{{ dateFormat "Sunday" "2015-01-21T20:54:45.847Z" }} → “Sunday”

So it seems to be that Monday is the representation of the full workday written. Important here is that all this is case-sensitive, so monday returns just a string called monday.

3-Letters-Weekdays

Head over to the 3-letters written weekdays like Mon, Tue, Wed … Here I expect the same behaviour as above - so lets check:

{{ dateFormat "Mon" "2015-01-21T20:54:45.847Z" }} → “Wed”

{{ dateFormat "Tue" "2015-01-21T20:54:45.847Z" }} → “Tue”
{{ dateFormat "Wed" "2015-01-21T20:54:45.847Z" }} → “Wed”
{{ dateFormat "Thu" "2015-01-21T20:54:45.847Z" }} → “Thu”
{{ dateFormat "Fri" "2015-01-21T20:54:45.847Z" }} → “Fri”
{{ dateFormat "Sat" "2015-01-21T20:54:45.847Z" }} → “Sat”
{{ dateFormat "Sun" "2015-01-21T20:54:45.847Z" }} → “Sun”

And the same case-sensitive rules apply here, mon simply returns the string back you have entered - mon.

1-Letter-Weekdays

Now check the common extreme shorthand version showing just the first letter of the weekday. Is that possible?

{{ dateFormat "M" "2015-01-21T20:54:45.847Z" }} → “M”
{{ dateFormat "T" "2015-01-21T20:54:45.847Z" }} → “T”
{{ dateFormat "W" "2015-01-21T20:54:45.847Z" }} → “W”
{{ dateFormat "T" "2015-01-21T20:54:45.847Z" }} → “T”
{{ dateFormat "F" "2015-01-21T20:54:45.847Z" }} → “F”
{{ dateFormat "S" "2015-01-21T20:54:45.847Z" }} → “S”
{{ dateFormat "S" "2015-01-21T20:54:45.847Z" }} → “S”

No, not at all. Every example above just returned what was putted into the the first parameter and was ignored.

If you actually need this, maybe a substring-Method helps you out here.

{{ substr (dateFormat "Mon" "2015-01-21T20:54:45.847Z") 0 1 }} → “W”
{{ substr (dateFormat "Monday" "2015-01-21T20:54:45.847Z") 0 1 }} → “W”

Substring is pretty easy to understand its syntax is substr <String> <start> <length>.

Years

Well, the year should be pretty clear - its YYYY - yes? Ok, no. It’s 2006. Hmmm? Let’s check what some of the other numbers returns?

{{ dateFormat "2006" "2015-01-21T20:54:45.847Z" }} → “2015”

{{ dateFormat "2005" "2015-01-21T20:54:45.847Z" }} → “21045”
{{ dateFormat "2007" "2015-01-21T20:54:45.847Z" }} → “21007”
{{ dateFormat "1998" "2015-01-21T20:54:45.847Z" }} → “1998”
{{ dateFormat "3026" "2015-01-21T20:54:45.847Z" }} → “8216”
{{ dateFormat "YYYY" "2015-01-21T20:54:45.847Z" }} → “YYYY”

Wow, that’s where the confusion starts. One thing after another. The only thing that actually returned the expected output was 2006 - it really seems to be a constant that you need to use to show a 4 digit year.

For the other test numbers you might see some representations in the date already. We will cover them later in this article.

What about a 2-digit year, like 15 - this must be 06.

{{ dateFormat "06" "2015-01-21T20:54:45.847Z" }} → “15”

Touchdown! No I think I’m on the right track.

Months

January vs February

Starting with a fully written month name like March. How to achieve this? Try and error…

{{ dateFormat "January" "2015-09-21T20:54:45.847Z" }} → “September”

{{ dateFormat "February" "2015-01-21T20:54:45.847Z" }} → “February”
{{ dateFormat "March" "2015-01-21T20:54:45.847Z" }} → “March”
{{ dateFormat "April" "2015-01-21T20:54:45.847Z" }} → “April”
{{ dateFormat "May" "2015-01-21T20:54:45.847Z" }} → “May”
{{ dateFormat "June" "2015-01-21T20:54:45.847Z" }} → “June”
{{ dateFormat "July" "2015-01-21T20:54:45.847Z" }} → “July”
{{ dateFormat "August" "2015-01-21T20:54:45.847Z" }} → “August”
{{ dateFormat "September" "2015-01-21T20:54:45.847Z" }} → “September”
{{ dateFormat "October" "2015-01-21T20:54:45.847Z" }} → “October”
{{ dateFormat "November" "2015-01-21T20:54:45.847Z" }} → “November”
{{ dateFormat "December" "2015-01-21T20:54:45.847Z" }} → “December”

For this test I needed to change the date for the January string to get a reliable test result. Voilà - January is the only string that works. And as seen at the Weekdays they are case sensitive as well.

3-Letters-Month

Going the same path as with the fully written month name it should be Jan. Let’s make a prove here:

{{ dateFormat "Jan" "2015-09-21T20:54:45.847Z" }} → “Sep”

{{ dateFormat "Feb" "2015-01-21T20:54:45.847Z" }} → “Feb”
{{ dateFormat "Mar" "2015-01-21T20:54:45.847Z" }} → “Mar”
{{ dateFormat "Apr" "2015-01-21T20:54:45.847Z" }} → “Apr”
{{ dateFormat "May" "2015-01-21T20:54:45.847Z" }} → “May”
{{ dateFormat "Jun" "2015-01-21T20:54:45.847Z" }} → “Jun”
{{ dateFormat "Jul" "2015-01-21T20:54:45.847Z" }} → “Jul”
{{ dateFormat "Aug" "2015-01-21T20:54:45.847Z" }} → “Aug”
{{ dateFormat "Sep" "2015-01-21T20:54:45.847Z" }} → “Sep”
{{ dateFormat "Oct" "2015-01-21T20:54:45.847Z" }} → “Oct”
{{ dateFormat "Nov" "2015-01-21T20:54:45.847Z" }} → “Nov”
{{ dateFormat "Dec" "2015-01-21T20:54:45.847Z" }} → “Dec”

Jackpot!

2-Digits Months

Head over to the two digits months representation. My guess, its 01 - another prove is coming:

{{ dateFormat "01" "2015-09-21T20:54:45.847Z" }} → “09”

{{ dateFormat "02" "2015-01-21T20:54:45.847Z" }} → “21”
{{ dateFormat "03" "2015-01-21T20:54:45.847Z" }} → “08”
{{ dateFormat "04" "2015-01-21T20:54:45.847Z" }} → “54”
{{ dateFormat "05" "2015-01-21T20:54:45.847Z" }} → “45”
{{ dateFormat "06" "2015-01-21T20:54:45.847Z" }} → “15”
{{ dateFormat "07" "2015-01-21T20:54:45.847Z" }} → “07”
{{ dateFormat "08" "2015-01-21T20:54:45.847Z" }} → “08”
{{ dateFormat "09" "2015-01-21T20:54:45.847Z" }} → “09”
{{ dateFormat "10" "2015-01-21T20:54:45.847Z" }} → “10”
{{ dateFormat "11" "2015-01-21T20:54:45.847Z" }} → “11”
{{ dateFormat "12" "2015-01-21T20:54:45.847Z" }} → “121”

Wow! Got it again, but… it seems that other numbers are related to something completely different. But it proved me at least, that 01 is for a 2-digits month representation with leading zero if necessary. Check this again with a regular 2-digit month like November. Expected output is 11.

{{ dateFormat "01" "2015-11-21T20:54:45.847Z" }} → “11”

Ok, I understand it better piece by piece.

Single-Digit Months

Yeah, for sure it’s just the digit 1, right?

{{ dateFormat "1" "2015-09-21T20:54:45.847Z" }} → “9”

{{ dateFormat "2" "2015-01-21T20:54:45.847Z" }} → “21”
{{ dateFormat "3" "2015-01-21T20:54:45.847Z" }} → “8”
{{ dateFormat "4" "2015-01-21T20:54:45.847Z" }} → “54”
{{ dateFormat "5" "2015-01-21T20:54:45.847Z" }} → “45”
{{ dateFormat "6" "2015-01-21T20:54:45.847Z" }} → “6”
{{ dateFormat "7" "2015-01-21T20:54:45.847Z" }} → “7”
{{ dateFormat "8" "2015-01-21T20:54:45.847Z" }} → “8”
{{ dateFormat "9" "2015-01-21T20:54:45.847Z" }} → “9”
{{ dateFormat "10" "2015-01-21T20:54:45.847Z" }} → “10”
{{ dateFormat "11" "2015-01-21T20:54:45.847Z" }} → “11”
{{ dateFormat "12" "2015-01-21T20:54:45.847Z" }} → “121”

Nailed it! 1 stands for the Months in a single digit. Still unclear what all the other numbers are standing for, but I will figure it out very soon I think.

Days

As seen with the tests above it seems that number 2 represent the date and 02 is for a 2-digits day, right?

{{ dateFormat "2" "2015-01-21T20:54:45.847Z" }} → “21”
{{ dateFormat "2" "2015-01-05T20:54:45.847Z" }} → “5”
{{ dateFormat "02" "2015-01-21T20:54:45.847Z" }} → “21”
{{ dateFormat "02" "2015-01-05T20:54:45.847Z" }} → “05”

Yes! Got it!

Hours

24-Hours-Format

Checking the outputs above and looking for a 20 in the outputs. No luck :-( So try and error again. I will not spam the article with all the tests I did, the whole alphabet [Aa-Zz] - no luck. Finally I found it on the number 15 :-)

{{ dateFormat "15" "2015-01-21T20:54:45.847Z" }} → “20”
{{ dateFormat "15" "2015-01-21T08:54:45.847Z" }} → “08”

Unfortunately I did not found anything for a single digit number. It’s always 2-digits with a leading zero.

00 - 12am/pm-Format

As I found above it should be the number 3 that represents the hour in a am-pm-format. Lets try:

{{ dateFormat "3" "2015-01-21T20:54:45.847Z" }} → “8”
{{ dateFormat "3" "2015-01-21T21:09:03.327Z" }} → “9”
{{ dateFormat "3" "2015-01-21T23:09:03.001Z" }} → “11”
{{ dateFormat "03" "2015-01-21T20:54:45.847Z" }} → “08”
{{ dateFormat "03" "2015-01-21T21:09:03.327Z" }} → “09”
{{ dateFormat "03" "2015-01-21T23:09:03.001Z" }} → “11”

But how to find out if it is am or pm? Try and error…

{{ dateFormat "PM" "2015-01-21T08:54:45.847Z" }} → “AM”
{{ dateFormat "PM" "2015-01-21T20:54:45.847Z" }} → “PM”

{{ dateFormat "AM" "2015-01-21T08:54:45.847Z" }} → “AM”
{{ dateFormat "AM" "2015-01-21T20:54:45.847Z" }} → “AM”
{{ dateFormat "am" "2015-01-21T08:54:45.847Z" }} → “am”
{{ dateFormat "pm" "2015-01-21T20:54:45.847Z" }} → “pm”

Final result - PM - does the trick. As always its case sensitive.

Minutes

Hooray, this I already found out in my tests above. Seems that 4 represents the minutes. Test it out - I’m also feeling comfortable that 04 are minutes with a leading zero :-)

{{ dateFormat "4" "2015-01-21T20:54:45.847Z" }} → “54”
{{ dateFormat "04" "2015-01-21T08:09:45.847Z" }} → “09”

Check.

Seconds

Yes, I had the seconds in the testset above as well - 5 - is for the seconds and 05 should stand for the seconds with a leading zero.

{{ dateFormat "5" "2015-01-21T20:54:45.847Z" }} → “45”
{{ dateFormat "05" "2015-01-21T08:09:03.847Z" }} → “03”

Next - check. Only milliseconds, and timezone left…

Milliseconds

Golang uses nanoseconds while ISO8601 accepts milliseconds, lets have a look where to find the milliseconds…

Could not find it. Failed. - Ok, who needs the milliseconds in a template… (I know, its a lousy excuse - however - who needs milliseconds in a template?)

Timezones

Now, lets go a little bit deeper on edge cases. What about the timezones? Looks like MST stands for the timezone, in the test case, its UTC.

{{ dateFormat "MST" "2015-01-21T08:54:45.847Z" }} → “UTC”

Difference to GMT

How to get the actual difference to the GMT (General Mean Time).

{{ dateFormat "-0700" "2015-01-21T08:54:45.847Z" }} → “+0000” (±hhmm)
{{ dateFormat "-07:00" "2015-01-21T20:54:45.847Z" }} → “+00:00” (±hh:mm)
{{ dateFormat "-07" "2015-01-21T08:54:45.847Z" }} → “+00” (±hh)
{{ dateFormat "Z0700" "2015-01-21T20:54:45.847Z" }} → “Z” (Z or ±hhmm)
{{ dateFormat "Z07:00" "2015-01-21T20:54:45.847Z" }} → “Z” (Z or ±hhmm)
{{ dateFormat "Z07" "2015-01-21T08:54:45.847Z" }} → “Z” (Z or ±hh:mm)

Conclusion Table

Constant Description Example
Monday A full textual representation of the day of the week Monday through Sunday
Mon A textual representation of a day, three letters Mon through Sun
2006 A full numeric representation of a year, 4 digits 2017 or 1986
06 A two digit representation of a year 17 or 86
January A full textual representation of a month, such as January or March January through December
Jan A short textual representation of a month, three letters January through December
01 Numeric representation of a month, with leading zeros 01 through 12
1 Numeric representation of a month, without leading zeros 1 through 12
02 Day of the month, 2 digits with leading zeros 01 through 31
15 24-hour format of an hour with leading zeros 00 through 24
3 12-hour format of an hour without leading zeros 1 through 12
03 12-hour format of an hour with leading zeros 01 through 12
PM Indicator if AM or PM AM or PM
4 Minutes without leading zeros 1 through 59
04 Minutes with leading zeros 01 through 59
5 Seconds, without leading zeros 1 through 59
05 Seconds, with leading zeros 01 through 59
MST Timezone Example: UTC, MST
-0700 Difference to GMT as ±hhmm +0000
-07:00 Difference to GMT as ±hh:mm +00:00
-07 Difference to GMT as ±hh +00
Z0700 Difference to GMT as Z or ±hhmm Z or ±hhmm
Z07:00 Difference to GMT as Z or ±hh:mm Z or ±hh:mm
Z07 Difference to GMT as Z or ±hh Z or ±hh