How to render recurring events


#1

Hey folks,

Here’s a question. What is the best way to render recurring events in a Hugo page?

Many Hugo users might be familiar with this scenario by now:

Your website has an “Events” page and you use a Hugo data file (YAML, TOML, whatever) to store all of the events. Then you use a custom template that uses where to pick only events in the future and range to cycle through all of those events and then render them to the events page.

I want to do that but additionally have an event that is maybe, the first Wednesday of every month. Any ideas on how I can accomplish this?

I don’t have any final solution yet but I could imagine three possible implementations:

  1. Do it in preprocessing. I could have the events in a data file with a cron-like value, and then use a Bash script (or other scripting language) to “expand” that events into the data file itself. Then run Hugo.
  2. Create a build of Hugo that has a custom function that could accomplish this. I kind of like this route but I don’t know what it would be called or how that would event work.
  3. Do it with JavaScript. Regular events could be in the data file, and recurring events would be stored in a JavaScript snippet during rendering. Then, in production, JavaScript could run, take that information and calculate the events, then inject it into the page.

3 seems the easiest way but is less static. 2 is my favorite option but not sure where to start.

Any comments on my ideas or additional ideas are welcomed. :slight_smile:


#2

Friendly bump.

I’m definitely going to implement something and report back regardless.


#3

Maybe this will give you some ideas to play off of. Given this code:

{{ $t := time "2019-02-06" }}

Then $t is a Time struct. So perhaps you could loop through a list of dates, and if the weekday was Wednesday, and if etc etc, then do something.

Code Output
{{ $t | safeHTML }} 2019-02-06 00:00:00 +0000 UTC
{{ $t.Day }} 6
{{ $t.Weekday }} Wednesday
{{ $t.Month }} February
{{ printf "%d" $t.Month }} 2
{{ $t.Year }} 2019
{{ $t.YearDay }} 37

#4

Hi,

If by “rendering” you mean simply printing out to the page a list of events that happen every X interval, this is how I would probably do it:

Given events.json as below. I would define interval to correspond to input that AddDate would take.

{ 
  "format": "Monday, January 2, 2006",
  "recurring": [
    {
      "title" : "(Annual + 1 Day) Event",
      "interval": [1,0,1],
      "start": "2019-02-01",
      "iterations": 5,
      "end": "2022-02-01"
    }
    ...
  ]
}

Then render like this:

{{ $recurring := .Site.Data.events.recurring }}
{{ $dateformat := cond (isset .Site.Data.events "format") (.Site.Data.events.format) "January 2, 2006" }}

{{ range $recurring }}
  {{ if isset . "start" }}
    {{ $start := .start | time }}
    {{ $iterations := cond (isset . "iterations") .iterations 1 }}
    {{ $end := ( cond (isset . "end") .end $start ) | time }}
    {{ $event := cond (isset . "title" ) .title "Event" }}
    {{ $interval := cond (isset . "interval") .interval ( slice 0 0 1 ) }}

    {{ range $i, $num := (seq $iterations) }}
      <h3>{{$event}} {{$num}}</h3>
      
      {{ $currentevent := $start.AddDate (index $interval 0 | mul $i | int) (index $interval 1 | mul $i | int) (index $interval 2 | mul $i | int) }}

      {{ if and ( $currentevent.After $end ) (ne $end $start) }}
        event {{$num}} has date ({{$currentevent.Format $dateformat}}) greater than end date {{$end.Format $dateformat}}<br>
      {{ else if (now.After $currentevent) }}
        This event finished on {{ $currentevent.Format $dateformat}}
      {{ else }}
        {{ $currentevent.Format $dateformat}}
      {{ end }}

    {{ end }}
  {{ end }}
{{end}}

This should print out:

(Annual + 1 Day) Event 1

This event finished on Friday, February 1, 2019

(Annual + 1 Day) Event 2

Sunday, February 2, 2020

(Annual + 1 Day) Event 3

Wednesday, February 3, 2021

(Annual + 1 Day) Event 4

event 4 has date (Friday, February 4, 2022) greater than end date Tuesday, February 1, 2022

(Annual + 1 Day) Event 5

event 5 has date (Sunday, February 5, 2023) greater than end date Tuesday, February 1, 2022


You could also render the recurring events mixed in with regular events by pushing all the ordinary events into an $allevents array, then adding the recurring events using the $currentevent value calculated above as their actual/event date. Then sort and range over $allevents to render.

I hope that all sorta makes sense (and that I answered the correct question :sweat_smile: )


#5

Thank you, I’ll be taking a look at these.


#6

To add to @pointyfar’s helpful reply, the below code doesn’t depend on a data file. It takes today’s date, then loops through everyday until a year from now. If the day is a Wednesday, it adds it to a list. It would still need additional logic to filter out the first Wednesday of the month, though.

{{- $days := seq 365 -}}
{{- $now := now -}}
{{- $list := slice -}}

{{- range $days -}}
  {{- $now = $now.AddDate 0 0 1 -}}
  {{- if eq (string $now.Weekday) "Wednesday" -}}
    {{- $list = $list | append $now -}}
  {{- end }}
{{- end -}}

{{- range $list }}
  {{ .Format "Mon Jan 2 2006" }}
{{- end }}
Output

Wed Feb 13 2019
Wed Feb 20 2019
Wed Feb 27 2019
Wed Mar 6 2019
Wed Mar 13 2019
Wed Mar 20 2019
Wed Mar 27 2019
Wed Apr 3 2019
Wed Apr 10 2019
Wed Apr 17 2019
Wed Apr 24 2019
Wed May 1 2019
Wed May 8 2019
Wed May 15 2019
Wed May 22 2019
Wed May 29 2019
Wed Jun 5 2019
Wed Jun 12 2019
Wed Jun 19 2019
Wed Jun 26 2019
Wed Jul 3 2019
Wed Jul 10 2019
Wed Jul 17 2019
Wed Jul 24 2019
Wed Jul 31 2019
Wed Aug 7 2019
Wed Aug 14 2019
Wed Aug 21 2019
Wed Aug 28 2019
Wed Sep 4 2019
Wed Sep 11 2019
Wed Sep 18 2019
Wed Sep 25 2019
Wed Oct 2 2019
Wed Oct 9 2019
Wed Oct 16 2019
Wed Oct 23 2019
Wed Oct 30 2019
Wed Nov 6 2019
Wed Nov 13 2019
Wed Nov 20 2019
Wed Nov 27 2019
Wed Dec 4 2019
Wed Dec 11 2019
Wed Dec 18 2019
Wed Dec 25 2019
Wed Jan 1 2020
Wed Jan 8 2020
Wed Jan 15 2020
Wed Jan 22 2020
Wed Jan 29 2020
Wed Feb 5 2020