Converting time variable between timezones

Hey @bep, currently Hugo has no ability to convert the time zone of a time.Time variable, like it is possible in pure Go. This makes it challenging to present the same date in different time zones. The only solution I am aware of is courtesy of @jmooring here Present a date object in different timezones where he created a helper function for calculating the offsets on date strings. Needless to say, this approach is suboptimal as it is very hard to account for all the intricacies of the time zone conversion in this way.

Example in Go:

package main

import (
	"fmt"
	"time"
)

const kyivTZ = "Europe/Kyiv"
const vancouverTZ = "America/Vancouver"

func main() {
	t := time.Now().UTC()
	locKyiv, _ := time.LoadLocation(kyivTZ)
	locVancouver, _ := time.LoadLocation(vancouverTZ)

	fmt.Printf("%v in UTC\n", t)
	fmt.Printf("%v in %v\n", t.In(locKyiv), kyivTZ)
	fmt.Printf("%v in %v\n", t.In(locVancouver), vancouverTZ)
}

// Output:
// 2022-12-12 04:21:11.206403 +0000 UTC in UTC
// 2022-12-12 06:21:11.206403 +0200 EET in Europe/Kyiv
// 2022-12-11 20:21:11.206403 -0800 PST in America/Vancouver

@jmooring’s helper function:

{{/*
Returns the given time in the target time zone.

Works with whole and fractional offsets (examples: -02:00, +05:30)

@param {time.Time} time The time in the source time zone.
@param {string} tz The target time zone.

@returns {time.Time} The time in the target time zone.

@example {{ partial "time-in-location.html" (dict "time" .Date "tz" "Asia/Calcutta") }}
*/}}

{{/* Initialize. */}}
{{ $time := 0 }}
{{ $targetTZ := "" }}
{{ $msg := "The time-in-location partial requires a dictionary with date and tz keys." }}

{{/* Get params. */}}
{{ if reflect.IsMap . }}
  {{ with .time }}
    {{ $time = . | time.AsTime }}
    {{ with $.tz }}
      {{ $targetTZ = . }}
    {{ else }}
      {{ errorf $msg }}
    {{ end }}
  {{ else }}
    {{ errorf $msg }}
  {{ end }}
{{ else  }}
  {{ errorf $msg }}
{{ end }}

{{/* Get offset from UTC, in fractional hours, in target time zone. */}}
{{ $temp := time.AsTime ($time.UTC.Format "2006-01-02T15:04:05") $targetTZ }}
{{ $temp = substr ($temp.Format "2006-01-02T15:04:05-07:00") -6 }}
{{ $offsetSign := substr $temp 0 1 }}
{{ $offsetHours := substr $temp 1 2 | strings.TrimPrefix "0" | float }}
{{ $offsetMinutes := substr $temp 4 2 | strings.TrimPrefix "0" | float }}
{{ $offsetHoursFractional := 0 }}
{{ if eq $offsetSign "+" }}
  {{ $offsetHoursFractional = add $offsetHours (div $offsetMinutes 60) | float }}
{{ else }}
  {{ $offsetHoursFractional = sub 0 (add $offsetHours (div $offsetMinutes 60)) | float }}
{{ end }}

{{/* Determine time in target time zone. */}}
{{ $temp = $time.UTC.Add (time.ParseDuration (printf "%fh" $offsetHoursFractional)) }}
{{ $targetTime := time.AsTime ($temp.Format "2006-01-02T15:04:05") $targetTZ }}

{{/* Return value */}}
{{ return $targetTime }}

Does it make sense to create a new helper function to manipulate a time.Time object itself, in this case converting the location?

Its usage can look like this:

{{ $t := .Date }}
{{ $t.InLoc("Europe/Kyiv").Format "2006-01-02T15:04:05-0700" }} // outputs "2022-12-12T06:21:11+0200"
{{ $t.InLoc("America/Vancouver").Format "2006-01-02T15:04:05-0700" }} // outputs "2022-12-11T20:21:11-0800"