Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

last_updated variable from sphinx build wrongly assumes UTC timezone #6527

Closed
ViktorHaag opened this issue Jun 28, 2019 · 1 comment
Closed
Labels
Milestone

Comments

@ViktorHaag
Copy link
Contributor

ViktorHaag commented Jun 28, 2019

Describe the bug
Because of an interaction between the way that the Sphinx html builder generates a last_updated datetime, and the assumptions made by the underlying babel library on formatting the time, the "last updated time" used in Sphinx templates is assumed to be a UTC time value, when that's not the case.

To Reproduce

  • Use a generic Sphinx document with a template that includes "last_updated" variable in the output.
  • In conf.py set html_last_updated_fmt = "%Y-%m-%d (%H:%M %Z)" ; this will prompt the last updated date-time format used to print out the timezone info as part of the formatted date-time string.
  • Build the docs: the timezone in the output will get reported as UTC, but the actual time will use whatever date-time/timezone your local system clock is set to.

Expected behavior
Either the build code should properly determine what the local platform's timezone is in order to capture that information in the datetime passed on to the babel formatter, or the builder code should always create a datetime that captures the system clock time in UTC so that babel correctly reports the timezone when it claims the timezone is UTC.

Environment info

  • OS: MacOS 10.14.5
  • Python version: 3.7.3
  • Sphinx version: 2.1.2
  • Sphinx extensions: HTML builder used
  • Extra tools: n/a

Additional context

  • Starting about line 482 in /sphinx/builders/html is this call to the i18n.format_date function (note that this only passes in a format string, and a locale/language, not a date-time value itself):
...
        # format the "last updated on" string, only once is enough since it
        # typically doesn't include the time of day
        lufmt = self.config.html_last_updated_fmt
        if lufmt is not None:
            self.last_updated = format_date(lufmt or _('%b %d, %Y'),
                                            language=self.config.language)
        else:
            self.last_updated = None
...
  • Starting about line 280 in /sphinx/util/i18n.py is the definition for the format_date function:
...
def format_date(format, date=None, language=None):
    # type: (str, datetime, str) -> str
    if date is None:
        # If time is not specified, try to use $SOURCE_DATE_EPOCH variable
        # See https://wiki.debian.org/ReproducibleBuilds/TimestampsProposal
        source_date_epoch = os.getenv('SOURCE_DATE_EPOCH')
        if source_date_epoch is not None:
            date = datetime.utcfromtimestamp(float(source_date_epoch))
        else:
            date = datetime.now()
...

If the build environment does not have SOURCE_DATE_EPOCH set, then the date value that's eventually passed on to the babel formatter is a naive datetime object with the call to datetime.now().

  • The babel formatter makes the decision that if it gets a datetime that's timezone-naive to interpret the value as a UTC datetime, and thus the expansion/formatting of %Z reports the time as UTC even though it won't be, unless your system clock is set to UTC.

I believe that one way to fix this would be to change the assignment to date like this:

        if source_date_epoch is not None:
            date = datetime.utcfromtimestamp(float(source_date_epoch))
        else:
            date = datetime.utcnow()

This would ensure that the datetime used for the build time is always a UTC time value (and the UTC version of the 'now' system time). This would ensure that at least "last updated" would always report the UTC value of when the build happened. However, it would also make localization of that time value to a timezone probably challenging for Sphinx users.

datetime.now() can accept a tzinfo so that the datetime created is not a timezone naive value, and then the babel formatter will properly render whatever the timezone actually is, but this raises the question about where that tzinfo would actually come from. Some options might be:

  • Determine it from the local system somehow?
  • Have it be a configured value in conf.py?

I think the more practical answer is to just use datetime.utcnow() to get the UTC time value, since if the local system had SOURCE_DATE_EPOCH set, the time would be a UTC time.

I will submit a PR that makes this change.

@tk0miya
Copy link
Member

tk0miya commented Jul 6, 2019

Fixed. Thanks!

@tk0miya tk0miya closed this as completed Jul 6, 2019
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 2, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests

2 participants