Generate copyright date from .Lastmod for non-homepage indexes such as /blog/ etc

I currently use the following code to generate the date included in the boilerplate copyright text in the footer of my site:

{{- if .IsHome -}}
  2005-{{ .Site.LastChange.Format "2006" }}
{{- else -}}
  {{ .Date.Format "2006" }}
{{- end -}}

This covers the case of the home page (where the range of dates goes from the date of the oldest content, which can be hardcoded, to the most recent, which {{ .Site.LastChange.Format "2006" }} has covered.

It also does what I want for individual pages (articles, in my case), where the date displayed will be the date on which the article was initially written (rather than the date when it was last modified, which some users might prefer).

This leaves, however, the issue of non-homepage indexes, such as /blog/ or, in my case, the articles and photos pages, where the corresponding results ( Copyright Β© Donald Jenkins 2018 and Copyright Β© Donald Jenkins 2019, respectively) merely display the date-parameter included in the front matter of the corresponding index pages in the /layout section, when, in fact, what one would want to display is a range of dates β€” like on the front page β€” but this time stretching from the date recorded in the front matter of the oldest page in the /articles and /photos sections respectively, and the most recent one.

Unfortunately, I haven’t been able to find a way of doing this, but suspect it’s possible. Any suggestions as to how would be very gratefully received.

Does your project have nested sections?

1) This?

content/
β”œβ”€β”€ products/
β”‚   β”œβ”€β”€ _index.md
β”‚   β”œβ”€β”€ product-1.md
β”‚   └── product-2.md
└── _index.md

2) Or this?

content/
β”œβ”€β”€ products/
β”‚   β”œβ”€β”€ benefits/
β”‚   β”‚   β”œβ”€β”€ benefit-1.md
β”‚   β”‚   β”œβ”€β”€ benefit-2.md
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ features/
β”‚   β”‚   β”œβ”€β”€ feature-1.md
β”‚   β”‚   β”œβ”€β”€ feature-2.md
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ _index.md
β”‚   β”œβ”€β”€ product-1.md
β”‚   └── product-2.md
└── _index.md

3) Or this?

content/
β”œβ”€β”€ products/
β”‚   β”œβ”€β”€ product-1/
β”‚   β”‚   β”œβ”€β”€ benefits/
β”‚   β”‚   β”‚   β”œβ”€β”€ benefit-1.md
β”‚   β”‚   β”‚   β”œβ”€β”€ benefit-2.md
β”‚   β”‚   β”‚   └── _index.md
β”‚   β”‚   β”œβ”€β”€ features/
β”‚   β”‚   β”‚   β”œβ”€β”€ feature-1.md
β”‚   β”‚   β”‚   β”œβ”€β”€ feature-2.md
β”‚   β”‚   β”‚   └── _index.md
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ product-2/
β”‚   β”‚   β”œβ”€β”€ benefits/
β”‚   β”‚   β”‚   β”œβ”€β”€ benefit-1.md
β”‚   β”‚   β”‚   β”œβ”€β”€ benefit-2.md
β”‚   β”‚   β”‚   └── _index.md
β”‚   β”‚   β”œβ”€β”€ features/
β”‚   β”‚   β”‚   β”œβ”€β”€ feature-1.md
β”‚   β”‚   β”‚   β”œβ”€β”€ feature-2.md
β”‚   β”‚   β”‚   └── _index.md
β”‚   β”‚   └── _index.md
β”‚   └── _index.md
└── _index.md

It looks like this:

content
β”œβ”€β”€ _index.md
β”œβ”€β”€ articles
β”‚   β”œβ”€β”€ _index.md
β”‚   β”œβ”€β”€ a-better-new-decade.md
β”‚   β”œβ”€β”€ a-plea-in-support-of-mr-obamas-perfectible-health-care-bill.md
β”‚   β”œβ”€β”€ a-time-to-gather-stones-together.md
β”‚   β”œβ”€β”€ abomination-in-iran.md
β”‚   β”œβ”€β”€ alternatives-to-google-search-using-glims-with-blekko-duck-duck-go-or-wolfram-alpha-in-safari 2.md
β”‚   β”œβ”€β”€ alternatives-to-google-search-using-glims-with-blekko-duck-duck-go-or-wolfram-alpha-in-safari.md
β”‚   β”œβ”€β”€ an-amazing-mixture-of-roland-petit-the-corps-de-ballet-and-proust.md
β”‚   β”œβ”€β”€ and-the-word-was-made-flesh-got-that.md
β”‚   β”œβ”€β”€ backing-up-a-mac-to-amazon-s3-with-arq-the-easiest-safest-and-most-accurate-solution.md
β”‚   β”œβ”€β”€ benedict-xvi-a-traditionalist-yet-more-in-tune-with-the-realities-of-this-age-than-his-predecessor.md
β”‚   β”œβ”€β”€ benjamin-millepied-takes-paris-by-storm.md
β”‚   β”œβ”€β”€ blogging-sequentially-using-writeroom-textmate-and-ecto-or-marsedit.md
β”‚   β”œβ”€β”€ brexit-time-to-move-on.md
β”‚   β”œβ”€β”€ brexit-would-be-constitutional-and-economic-suicide.md
β”‚   β”œβ”€β”€ can-the-fifth-republic-survive-m-sarkozy.md
β”‚   β”œβ”€β”€ capote-and-the-death-penalty-turning-the-clock-back-to-1967.md
β”‚   β”œβ”€β”€ cheap-monday-or-how-to-be-thin-poor-and-fashionable.md
β”‚   β”œβ”€β”€ chopin-such-appropriate-music-for-a-ballet.md
β”‚   β”œβ”€β”€ connecting-wordpress-lightroom-and-apple-photos.md
β”‚   β”œβ”€β”€ did-you-even-know-facebook-has-unilaterally-decided-to-share-all-your-data-with-anyone-it-pleases.md
β”‚   β”œβ”€β”€ does-being-for-the-liberty-of-england-still-mean-being-for-the-protestant-religion.md
β”‚   β”œβ”€β”€ does-french-tech-have-any-future-at-all.md
β”‚   β”œβ”€β”€ dont-let-the-french-president-get-elected-by-chance.md
β”‚   β”œβ”€β”€ feminists-and-homophobes-have-destroyed-a-unique-four-hundred-and-fifty-year-old-english-compromise.md
β”‚   β”œβ”€β”€ googles-project-fi-a-flexible-and-truly-global-mobile-solution-for-international-travellers.md
β”‚   β”œβ”€β”€ how-i-effortlessly-draft-and-maintain-squeaky-clean-css-with-sass-and-compass-app.md
β”‚   β”œβ”€β”€ i-guess-a-moratorium-if-we-get-one-is-better-than-nothing.md
β”‚   β”œβ”€β”€ i-have-been-known-to-enjoy-rosenkavalier-sometimes.md
β”‚   β”œβ”€β”€ idomeneo-a-lyrical-turning-point.md
β”‚   β”œβ”€β”€ improve-your-wordpress-blog-with-marsedit-amazon-cloudfront-and-markdown.md
β”‚   β”œβ”€β”€ introducing-policymakr.md
β”‚   β”œβ”€β”€ is-nancy-mitford-no-longer-understood.md
β”‚   β”œβ”€β”€ itunes-and-steve-jobss-walled-garden-a-major-strategic-blunder-for-apple.md
β”‚   β”œβ”€β”€ jonathan-littell-is-no-match-for-julien-green.md
β”‚   β”œβ”€β”€ la-dame-aux-camelias-crusty-old-fogeys-jumping-for-joy.md
β”‚   β”œβ”€β”€ non-human-animal-rights-the-categorical-imperative-of-our-time.md
β”‚   β”œβ”€β”€ of-the-virtues-of-representative-democracy.md
β”‚   β”œβ”€β”€ oh-so-the-pope-abolished-limbo-did-he.md
β”‚   β”œβ”€β”€ pooling-anglo-french-defence-a-sign-of-progress-or-of-decline.md
β”‚   β”œβ”€β”€ preparing-this-site-for-the-2020s.md
β”‚   β”œβ”€β”€ reeder-for-mac-a-stunning-implementation-of-minimalism-elegant-design-and-practicality-in-one-rss-client.md
β”‚   β”œβ”€β”€ reorganising-this-blog.md
β”‚   β”œβ”€β”€ should-france-change-her-national-anthem.md
β”‚   β”œβ”€β”€ sixty-years-a-queen-with-unflinching-grace-and-steadfast-devotion-to-duty.md
β”‚   β”œβ”€β”€ social-networking-going-towards-an-oligopolistic-closed-shop-system.md
β”‚   β”œβ”€β”€ sparrow-mail-the-most-elegant-powerful-and-minimalist-mac-email-client.md
β”‚   β”œβ”€β”€ stuff-i-couldnt-do-without-in-2011.md
β”‚   β”œβ”€β”€ stuff-i-couldnt-live-without-in-2010.md
β”‚   β”œβ”€β”€ switching-from-a-dv-to-a-ve-server-on-media-temple.md
β”‚   β”œβ”€β”€ test-article.md
β”‚   β”œβ”€β”€ the-absurdity-of-brexit.md
β”‚   β”œβ”€β”€ the-four-traditionalist-bishops-were-they-ever-validly-excommunicated.md
β”‚   β”œβ”€β”€ the-french-themselves-dont-realise-how-beautifully-french-les-troyens-is.md
β”‚   β”œβ”€β”€ the-man-who-dreamt-of-a-modern-rich-democratic-peaceful-iran.md
β”‚   β”œβ”€β”€ the-new-macbook-air-with-its-first-rate-gpu-and-ssd-its-real-life-performance-belies-the-paper-specs.md
β”‚   β”œβ”€β”€ the-welcome-trend-away-from-anonymous-posting-on-the-internet.md
β”‚   β”œβ”€β”€ turn-of-the-decade-tribulations.md
β”‚   β”œβ”€β”€ what-should-a-tory-government-stand-for-in-2010.md
β”‚   β”œβ”€β”€ whats-open-anyway-walled-gardens-will-never-stifle-innovation-and-shouldnt-be-confused-with-net-neutrality.md
β”‚   β”œβ”€β”€ when-in-france-dont-ever-wear-brown-shoes-after-6-pm.md
β”‚   β”œβ”€β”€ whither-frances-institutions-the-tragic-and-unlamented-end-of-a-thirty-year-golden-age.md
β”‚   β”œβ”€β”€ why-does-literature-seek-to-give-meaning-to-the-yearning-for-death.md
β”‚   β”œβ”€β”€ why-i-have-finally-decided-to-blog-in-my-own-name.md
β”‚   β”œβ”€β”€ why-ive-decided-to-keep-a-blog.md
β”‚   β”œβ”€β”€ will-mr-obama-bring-the-united-states-any-closer-to-abolishing-the-death-penalty.md
β”‚   β”œβ”€β”€ would-the-social-register-types-in-new-york-hate-haneke-too.md
β”‚   └── yvonne-princesse-de-bourgogne-gerard-mortier-shuts-his-bolt-pretty-triumphantly.md
β”œβ”€β”€ categories
β”‚   β”œβ”€β”€ _index.md
β”‚   β”œβ”€β”€ culture
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ literature
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ personal
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ politics
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ religion
β”‚   β”‚   └── _index.md
β”‚   β”œβ”€β”€ sundry
β”‚   β”‚   └── _index.md
β”‚   └── tech
β”‚       └── _index.md
β”œβ”€β”€ contact
β”‚   └── index.md
β”œβ”€β”€ legal
β”‚   β”œβ”€β”€ _index.md
β”‚   β”œβ”€β”€ privacy.md
β”‚   └── terms.md
└── photos
    └── _index.md

The pages for which I can’t work out how to display the correct range of copyright dates are: /articles, /photos, /legal, /contact and, last but not least, the dynamically-generated term pages (tags, categories)

OK, no nested sections. That makes it a bit easier.

If you omit date in the front matter of your _index.md files, the .Lastmod date in your section template should return the date of the last modified page.

Please verify.

For more complicated scenarios, I opened a proposal this morning that will help:
https://github.com/gohugoio/hugo/issues/11030

That would allow us to range through the pages, build a slice of dates, then get min and max.

1 Like

I wouldn’t worry about these. Since they are dynamically generated, just use the static start date, and now.Year as the end date.

No, unfortunately not. For /articles, it displays this:

SCR-20230527-qkod

Then for /tech, it displays 2023β€”as do all the other category pagesβ€”despite the fact the most recent article in that category is dated 2019. And /photos, for some reason, shows 2019…

I’ll need access to your repo.

You are pulling the last modified date from Git, but you don’t have an adequate commit history. For example, the commit date for β€œcontent/articles/a-better-new-decade.md” is in 2023, but the article was written in 2011.

Using the commit date for articles day-forward makes sense, but not for legacy pages.

What do you want to do?

1 Like

Ah, that explains it. The individual articles date back to 2005, but I only started committing them to Github when I switched to Hugo earlier this year. Would removing git from the list of last-modified dates in the config do the trick, like so:

[frontmatter]
date = ["date", "publishDate", "lastmod"]
lastmod = ["lastmod", "date", "publishDate"]
publishDate = ["publishDate", "date"]
expiryDate = ["expiryDate"]

?

Implementing your suggestion to remove the reference to git commits from the site Params, I’ve changed the code in the partial to:

{{- if .IsHome -}}
  2005-{{ .Site.LastChange.Format "2006" }}
{{ else if .IsPage }}
  {{ .Date.Format "2006" }}
{{ else if .Data.Singular }}
  2005-{{ now.Year }}
{{ else if eq .Params.slug "photos" }}
  1933-{{ .Date.Format "2006" }}
{{ else }}
  2005-{{ .Date.Format "2006" }}
{{- end -}}

1. Taxonomy terms pages (targeted by {{ else if .Data.Singular }}) now display the standard start date of 2005 and the current date as the end date (not strictly true, since I actually only started writing about some categories in later years and they don’t all feature a post written in the current year, but there’s probably no obvious way of giving the true range in this case).

2. /articles now lists the correct end-of-range date, with the start date also being arbitrarily set to 2005 (but this is not a problem in their case).

3. /photos, rather eerily, displays the correct end date (2019, which is the date of the most recent photograph on that page) despite the fact I didn’t include a date parameter in the json files I’m using to display photos, and also correctly displays 1933 (date of the oldest photograph) as the start date (I wasn’t born then, but can claim to be the successor to the original copyright owners).

This is a clear improvement on the previous setup. The only thing that still needs doing is arranging for the Lastmod date (which I don’t want to use in the other pages) to be displayed on the /legal/terms (terms) and /legal/privacy (privacy policy) pages, since in this specific case the only possible correct date is the date of the last change. Is there a way of specifically targeting the child pages of /legal in the conditional? I haven’t found one.

Do a check on .Page.Parent.something.

Hm.

{{ else if eq .Page.Parent "legal" }}
  {{ .Lastmod.Format "2006" }}

doesn’t seem to do the trick.

You missed the something part.

.Page.Parent.Title, etc.

Or compare to .Page.Section

Any combination of .Page.Parent.something I try throws up the date for .Date rather than for .Lastmod. I conclude from this that the reason is how .Lastmod and .Date are set by Hugo and how they relate to each other. As I understand, in Go one accesses publish date and last modified date in one of the two following ways:

  1. editor-controlled via front-matter in Markdown;

  2. automatically extracted from git.

I didn’t want to have to manually update the last modified date in my front matter every time I amended a page, so I didn’t bother to include it, and I use a text editor client to write new posts, so archetypes aren’t relevant to my workflow for new content.

1. In config.toml, I have:

[frontmatter]
date = ["date", "publishDate", "lastmod"]
lastmod = ["lastmod", "date", "publishDate"]
publishDate = ["publishDate", "date"]
expiryDate = ["expiryDate"]

2. .Lastmod isn’t set in my front matter. No longer pulling it from :git (an idea I originally got here), as we decided to do earlier, solved the issue for the section pages, but it now means I have no way of targeting the .Lastmod in the /legal section.

In other words, I’ve got myself in a configuration in which, when I ask Hugo to display lastmod, it displays date instead.

I tried adding lastmod: ":git" to the two /legal section pages’ front matter, but that didn’t work.

And adding lastmod = [":fileModTime", β€œ:default”] to config.toml, which I saw suggested here, doesn’t work either.

In fact, even hard-coding lastmod: 2023-05-26T03:20:07+00:00 to their front matter still displays the date!

Yet bizarrely, the following partial works, as you can see on the tope of the terms and privacy pages, at the top of which it correctly generates lastmod providing it is set to ['lastmod', ':fileModTime', ':default'] in the front matter, using :

<span
  >&thinsp;{{ .Lastmod.Format "2" | humanize }} {{ .Lastmod.Format "January" }},
  {{ .Lastmod.Format "2006" }}</span
>

and despite displaying date rather than lastmod in the footer using this code, on the same page:

{{ else if or (eq .Section "legal") (eq .Params.slug "terms") (eq .Params.slug "privacy") }}
  {{ .Lastmod.Format "2006" }}

I’m guessing the solution is to set separate [frontmatter] defaults for that section. Right?

Just in case anyone is still watching this rather convoluted topic, I solved the issue in the following way:

{{- if .IsHome -}}
  2005-{{ .Site.LastChange.Format "2006" }}
{{ else if .IsPage }}
  {{ .Date.Format "2006" }}
{{ else if .Data.Singular }}
  2005-{{ now.Year }}
{{ else if eq .Params.slug "photos" }}
  1965-{{ .Date.Format "2006" }}
{{ else if or (eq .Section "legal") (eq .Params.slug "terms") (eq .Params.slug "privacy") }}
  {{ .Lastmod.Format "2006" }}
{{ else }}
  2005-{{ .Lastmod.Format "2006" }}
{{- end -}}

This is with the following date settings in config.toml:

[frontmatter]
date = ["date", "publishDate", "lastmod"]
lastmod = ['lastmod', ':fileModTime', ':default']
publishDate = ["publishDate", "date"]
expiryDate = ["expiryDate"]

and this in the footer:

{{ if or (eq .Section "policies") (eq .Params.slug "contact") }}
{{ partial "icons/unlicense.html" . }}&thinsp;This content is <a href="https://github.com/donaldjenkins/Policies/blob/main/LICENSE">unlicensed</a>
{{ else }}
Copyright &#169; Donald Jenkins {{ partial "footer/copyright-year.html" . }}
{{ end }}

For any content that doesn’t by nature, despite qualifying as β€˜original works of authorship’ require copyright protection, such as the contact form and legal policies, displaying a copyright notice serves no purpose, and in these cases I have set the Hugo conditional to display the unlicense notice.

For any pages for which you need to use lastmod, but haven’t explicitely included it in the post’s front matter, using :fileModTime provides a robust alternative to :git.

To sum up the problem this solves, there was an incompatibility, for anyone who doesn’t have a git commit history that matches the age of their content (surely quite a frequent occurrence) between using :git to set lastmod, which was the only way of showing a coherent copyright date for pages where it is desirable to display the lastmod date rather than the date of first creation, and displaying the full range of years for list pages, which will only show up correctly if date is removed from the front matter of the index page and :git is unset as a source for lastmod.

One possible avenue for solving this would be the ability to set front matter by section, but I don’t believe it’s currently possible. Since copyright protection isn’t required for policies and contact forms in the first place, not claiming copyright for these sorts of content seems a sensible solution.

This is too much work. Just use now.Format "2006" and everything will have the current date. Why would you not extend the copyright on every page?

Because it is dishonest.

It’s not dishonest. The copyright is extended by the act of just writing the date on the page. That’s how copyright works post Copyright Act of 1976 - Wikipedia.