diff --git a/Makefile.am b/Makefile.am
index 803e9d00df3e2db4bf8a8c3146d8f2d8a7e94c7e..cbe94ad937a5a1a93a923178fbc97eb9aed4c392 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -416,7 +416,7 @@ endif
 
 .PHONY: update-versions
 update-versions:
-	$(PERL) $(top_builddir)/scripts/maint/updateVersions.pl
+	abs_top_srcdir="$(abs_top_srcdir)" $(PYTHON) $(top_srcdir)/scripts/maint/update_versions.py
 
 .PHONY: callgraph
 callgraph:
diff --git a/configure.ac b/configure.ac
index 31e41c3bbc13f09f2415d3b0e7acd4434f654c1b..7f0d375440622afad495d0ce161849c2807d3241 100644
--- a/configure.ac
+++ b/configure.ac
@@ -8,6 +8,15 @@ AC_INIT([tor],[0.4.0.0-alpha-dev])
 AC_CONFIG_SRCDIR([src/app/main/tor_main.c])
 AC_CONFIG_MACRO_DIR([m4])
 
+# DO NOT EDIT THIS DEFINITION BY HAND UNLESS YOU KNOW WHAT YOU'RE DOING.
+#
+# The update_versions.py script updates this definition when the
+# version number changes.  Tor uses it to make sure that it
+# only shuts down for missing "required protocols" when those protocols
+# are listed as required by a consensus after this date.
+AC_DEFINE(APPROX_RELEASE_DATE, ["2019-01-15"], # for 0.4.0.0-alpha-dev
+          [Approximate date when this software was released. (Updated when the version changes.)])
+
 # "foreign" means we don't follow GNU package layout standards
 # "1.11" means we require automake version 1.11 or newer
 # "subdir-objects" means put .o files in the same directory as the .c files
@@ -2417,7 +2426,6 @@ AC_CONFIG_FILES([
 	src/config/torrc.minimal
 	src/rust/.cargo/config
 	scripts/maint/checkOptionDocs.pl
-	scripts/maint/updateVersions.pl
 ])
 
 if test "x$asciidoc" = "xtrue" && test "$ASCIIDOC" = "none"; then
diff --git a/doc/HACKING/ReleasingTor.md b/doc/HACKING/ReleasingTor.md
index b5444afa96fd4575ef9f12a8dcdcb6b6eb24491b..b260cdbb195d59a25e9f5a26c7b5a956ca088f9e 100644
--- a/doc/HACKING/ReleasingTor.md
+++ b/doc/HACKING/ReleasingTor.md
@@ -131,13 +131,9 @@ new Tor release:
 === III. Making the source release.
 
 1. In `maint-0.?.x`, bump the version number in `configure.ac` and run
-   `perl scripts/maint/updateVersions.pl` to update version numbers in other
+   `make update-versions` to update version numbers in other
    places, and commit.  Then merge `maint-0.?.x` into `release-0.?.x`.
 
-   (NOTE: To bump the version number, edit `configure.ac`, and then run
-   either `make`, or `perl scripts/maint/updateVersions.pl`, depending on
-   your version.)
-
    When you merge the maint branch forward to the next maint branch, or into
    master, merge it with "-s ours" to avoid a needless version bump.
 
diff --git a/scripts/maint/updateVersions.pl.in b/scripts/maint/updateVersions.pl.in
deleted file mode 100755
index 65c51a1f2df849274b338594c2bd0a8b33f7b770..0000000000000000000000000000000000000000
--- a/scripts/maint/updateVersions.pl.in
+++ /dev/null
@@ -1,59 +0,0 @@
-#!/usr/bin/perl -w
-
-$CONFIGURE_IN = '@abs_top_srcdir@/configure.ac';
-$ORCONFIG_H = '@abs_top_srcdir@/src/win32/orconfig.h';
-$TOR_NSI = '@abs_top_srcdir@/contrib/win32build/tor-mingw.nsi.in';
-
-$quiet = 1;
-
-sub demand {
-    my $fn = shift;
-    die "Missing file $fn" unless (-f $fn);
-}
-
-demand($CONFIGURE_IN);
-demand($ORCONFIG_H);
-demand($TOR_NSI);
-
-# extract version from configure.ac
-
-open(F, $CONFIGURE_IN) or die "$!";
-$version = undef;
-while (<F>) {
-    if (/AC_INIT\(\[tor\],\s*\[([^\]]*)\]\)/) {
-	$version = $1;
-	last;
-    }
-}
-die "No version found" unless $version;
-print "Tor version is $version\n" unless $quiet;
-close F;
-
-sub correctversion {
-    my ($fn, $defchar) = @_;
-    undef $/;
-    open(F, $fn) or die "$!";
-    my $s = <F>;
-    close F;
-    if ($s =~ /^$defchar(?:)define\s+VERSION\s+\"([^\"]+)\"/m) {
-	$oldver = $1;
-	if ($oldver ne $version) {
-	    print "Version mismatch in $fn: It thinks that the version is $oldver.  I think it's $version.  Fixing.\n";
-	    $line = $defchar . "define VERSION \"$version\"";
-	    open(F, ">$fn.bak");
-	    print F $s;
-	    close F;
-	    $s =~ s/^$defchar(?:)define\s+VERSION.*?$/$line/m;
-	    open(F, ">$fn");
-	    print F $s;
-	    close F;
-	} else {
-	    print "$fn has the correct version. Good.\n" unless $quiet;
-	}
-    } else {
-	print "Didn't find a version line in $fn -- uh oh.\n";
-    }
-}
-
-correctversion($TOR_NSI, "!");
-correctversion($ORCONFIG_H, "#");
diff --git a/scripts/maint/update_versions.py b/scripts/maint/update_versions.py
new file mode 100755
index 0000000000000000000000000000000000000000..8067f2c6c8e13994bf3be134f52a37ffcebc3d44
--- /dev/null
+++ b/scripts/maint/update_versions.py
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+
+import io
+import os
+import re
+import sys
+import time
+
+def P(path):
+    """
+    Give 'path' as a path relative to the abs_top_srcdir environment
+    variable.
+    """
+    return os.path.join(
+        os.environ.get('abs_top_srcdir', "."),
+        path)
+
+def warn(msg):
+    """
+    Print an warning message.
+    """
+    print("WARNING: {}".format(msg), file=sys.stderr)
+
+def find_version(infile):
+    """
+    Given an open file (or some other iterator of lines) holding a
+    configure.ac file, find the current version line.
+    """
+    for line in infile:
+        m = re.search(r'AC_INIT\(\[tor\],\s*\[([^\]]*)\]\)', line)
+        if m:
+            return m.group(1)
+
+    return None
+
+def update_version_in(infile, outfile, regex, versionline):
+    """
+    Copy every line from infile to outfile. If any line matches 'regex',
+    replace it with 'versionline'.  Return True if any line was changed;
+    false otherwise.
+
+    'versionline' is either a string -- in which case it is used literally,
+    or a function that receives the output of 'regex.match'.
+    """
+    found = False
+    have_changed = False
+    for line in infile:
+        m = regex.match(line)
+        if m:
+            found = True
+            oldline = line
+            if type(versionline) == type(u""):
+                line = versionline
+            else:
+                line = versionline(m)
+            if not line.endswith("\n"):
+                line += "\n"
+            if oldline != line:
+                have_changed = True
+        outfile.write(line)
+
+    if not found:
+        warn("didn't find any version line to replace in {}".format(infile.name))
+
+    return have_changed
+
+def replace_on_change(fname, change):
+    """
+    If "change" is true, replace fname with fname.tmp.  Otherwise,
+    delete fname.tmp.  Log what we're doing to stderr.
+    """
+    if not change:
+        print("No change in {}".format(fname))
+        os.unlink(fname+".tmp")
+    else:
+        print("Updating {}".format(fname))
+        os.rename(fname+".tmp", fname)
+
+
+def update_file(fname,
+                regex,
+                versionline,
+                encoding="utf-8"):
+    """
+    Replace any line matching 'regex' in 'fname' with 'versionline'.
+    Do not modify 'fname' if there are no changes made.  Use the
+    provided encoding to read and write.
+    """
+    with io.open(fname, "r", encoding=encoding) as f, \
+         io.open(fname+".tmp", "w", encoding=encoding) as outf:
+        have_changed = update_version_in(f, outf, regex, versionline)
+
+    replace_on_change(fname, have_changed)
+
+# Find out our version
+with open("configure.ac") as f:
+    version = find_version(f)
+
+# If we have no version, we can't proceed.
+if version == None:
+    print("No version found in configure.ac", file=sys.stderr())
+    sys.exit(1)
+
+print("The version is {}".format(version))
+
+today = time.strftime("%Y-%m-%d", time.gmtime())
+
+# In configure.ac, we replace the definition of APPROX_RELEASE_DATE
+# with "{today} for {version}", but only if the version does not match
+# what is already there.
+def replace_fn(m):
+    if m.group(1) != version:
+        # The version changed -- we change the date.
+        return u'AC_DEFINE(APPROX_RELEASE_DATE, ["{}"], # for {}'.format(today, version)
+    else:
+        # No changes.
+        return m.group(0)
+update_file(P("configure.ac"),
+            re.compile(r'AC_DEFINE\(APPROX_RELEASE_DATE.* for (.*)'),
+            replace_fn)
+
+# In tor-mingw.nsi.in, we replace the definition of VERSION.
+update_file(P("contrib/win32build/tor-mingw.nsi.in"),
+            re.compile(r'!define VERSION .*'),
+            u'!define VERSION "{}"'.format(version),
+            encoding="iso-8859-1")
+
+# In src/win32/orconfig.h, we replace the definition of VERSION.
+update_file(P("src/win32/orconfig.h"),
+            re.compile(r'#define VERSION .*'),
+            u'#define VERSION "{}"'.format(version))