From 471f59e6e1a5dffc23dab24c3c6eda966d06e898 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miguel=20=C3=81ngel=20Arruga=20Vivas?=
 <rosen644835@gmail.com>
Date: Fri, 11 Dec 2020 13:40:05 +0100
Subject: [PATCH] xgettext, msginit: Honor SOURCE_DATE_EPOCH for timestamps.

* gettext-tools/src/msginit.c (po_revision_date): Use po_current_time
instead of a direct time call.
* gettext-tools/src/po-time.c (TM_YEAR_ORIGIN_UTC,
TM_MONTH_ORIGIN_UTC, TM_MDAY_ORIGIN_UTC, TM_HOUR_ORIGIN_UTC,
TM_ISDST_ORIGIN_UTC): New constants for struct tm initialization.
(has_source_date_epoch, fill_source_date_epoch, po_current_time): New
functions.
(po_strftime): Call has_source_date_epoch to ensure that the date uses
UTC timezone when SOURCE_DATE_EPOCH is provided through the
environment.
* gettext-tools/src/po-time.h (po_current_time): New prototype.
* gettext-tools/src/xgettext.c (construct_header): Use po_current_time
instead of a direct time call.
* gettext-tools/tests/xgettext-3: Test a fixed POT-Creation-Date
instead of removing it from the generated file.
---
 gettext-tools/src/msginit.c    |  2 +-
 gettext-tools/src/po-time.c    | 77 +++++++++++++++++++++++++++++++---
 gettext-tools/src/po-time.h    |  9 +++-
 gettext-tools/src/xgettext.c   |  2 +-
 gettext-tools/tests/xgettext-3 |  8 ++--
 5 files changed, 87 insertions(+), 11 deletions(-)

diff --git a/gettext-tools/src/msginit.c b/gettext-tools/src/msginit.c
index bd17a08a7..50351b24b 100644
--- a/gettext-tools/src/msginit.c
+++ b/gettext-tools/src/msginit.c
@@ -1083,7 +1083,7 @@ po_revision_date (const char *header)
       /* Assume the translator will modify the PO file now.  */
       time_t now;
 
-      time (&now);
+      po_current_time (&now);
       return po_strftime (&now);
     }
 }
diff --git a/gettext-tools/src/po-time.c b/gettext-tools/src/po-time.c
index 74001cb79..1577bf0a8 100644
--- a/gettext-tools/src/po-time.c
+++ b/gettext-tools/src/po-time.c
@@ -1,5 +1,5 @@
 /* PO/POT file timestamps.
-   Copyright (C) 1995-1998, 2000-2003, 2006 Free Software Foundation, Inc.
+   Copyright (C) 1995-1998, 2000-2003, 2006, 2020 Free Software Foundation, Inc.
    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
 
    This program is free software: you can redistribute it and/or modify
@@ -22,10 +22,18 @@
 /* Specification.  */
 #include "po-time.h"
 
+#include <stdbool.h>
+#include <stdlib.h>
+
 #include "xvasprintf.h"
 
 
 #define TM_YEAR_ORIGIN 1900
+#define TM_YEAR_ORIGIN_UTC 1970 - TM_YEAR_ORIGIN
+#define TM_MONTH_ORIGIN_UTC 0
+#define TM_MDAY_ORIGIN_UTC 1
+#define TM_HOUR_ORIGIN_UTC 0
+#define TM_ISDST_ORIGIN_UTC -1
 
 /* Yield A - B, measured in seconds.  */
 static long
@@ -50,6 +58,26 @@ difftm (const struct tm *a, const struct tm *b)
 }
 
 
+/* See https://reproducible-builds.org/specs/source-date-epoch/  */
+static bool
+has_source_date_epoch (long *seconds)
+{
+  const char *source_date_epoch = getenv ("SOURCE_DATE_EPOCH");
+  if (source_date_epoch)
+    {
+      char *end;
+      long value = strtol (source_date_epoch, &end, 10);
+      if (end != source_date_epoch)
+        {
+          if (seconds)
+            *seconds = value;
+          return true;
+        }
+    }
+  return false;
+}
+
+
 char *
 po_strftime (const time_t *tp)
 {
@@ -59,12 +87,21 @@ po_strftime (const time_t *tp)
 
   local_time = *localtime (tp);
   tz_sign = '+';
-  tz_min = difftm (&local_time, gmtime (tp)) / 60;
-  if (tz_min < 0)
+  /* We have to workaround the localtime/mktime interface, as
+     SOURCE_DATE_EPOCH is specified based on UTC and not on the
+     current locale of the user, but that information is not provided
+     from the time_t itself.  */
+  if (!has_source_date_epoch (NULL))
     {
-      tz_min = -tz_min;
-      tz_sign = '-';
+      tz_min = difftm (&local_time, gmtime (tp)) / 60;
+      if (tz_min < 0)
+        {
+          tz_min = -tz_min;
+          tz_sign = '-';
+        }
     }
+  else
+    tz_min = 0;
   return xasprintf ("%d-%02d-%02d %02d:%02d%c%02ld%02ld",
                     local_time.tm_year + TM_YEAR_ORIGIN,
                     local_time.tm_mon + 1,
@@ -73,3 +110,33 @@ po_strftime (const time_t *tp)
                     local_time.tm_min,
                     tz_sign, tz_min / 60, tz_min % 60);
 }
+
+
+static bool
+fill_source_date_epoch (time_t *t)
+{
+  long seconds;
+  if (has_source_date_epoch (&seconds))
+    {
+      struct tm utc_time;
+
+      utc_time.tm_isdst = TM_ISDST_ORIGIN_UTC;
+      utc_time.tm_year = TM_YEAR_ORIGIN_UTC;
+      utc_time.tm_mon = TM_MONTH_ORIGIN_UTC;
+      utc_time.tm_mday = TM_MDAY_ORIGIN_UTC;
+      utc_time.tm_hour = TM_HOUR_ORIGIN_UTC;
+      utc_time.tm_sec = seconds;
+
+      *t = mktime (&utc_time);
+      return true;
+    }
+  return false;
+}
+
+
+void
+po_current_time (time_t *t)
+{
+  if (!fill_source_date_epoch (t))
+    time (t);
+}
diff --git a/gettext-tools/src/po-time.h b/gettext-tools/src/po-time.h
index a01484665..0ae6cc72c 100644
--- a/gettext-tools/src/po-time.h
+++ b/gettext-tools/src/po-time.h
@@ -1,5 +1,5 @@
 /* PO/POT file timestamps.
-   Copyright (C) 2001-2003 Free Software Foundation, Inc.
+   Copyright (C) 2001-2003, 2020 Free Software Foundation, Inc.
    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
 
    This program is free software: you can redistribute it and/or modify
@@ -31,6 +31,13 @@ extern "C" {
 extern char *po_strftime (const time_t *tp);
 
 
+/* Fill the current date into *tp, honoring SOURCE_DATE_EPOCH
+   environment variable when it is defined to a correct value.  Any
+   error on its value isn't signaled but just fallback to the usual
+   time call.  */
+extern void po_current_time (time_t *tp);
+
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/gettext-tools/src/xgettext.c b/gettext-tools/src/xgettext.c
index 1dce01efb..a0d89f3b9 100644
--- a/gettext-tools/src/xgettext.c
+++ b/gettext-tools/src/xgettext.c
@@ -2007,7 +2007,7 @@ the MSGID_BUGS_ADDRESS variable there; otherwise please\n\
 specify an --msgid-bugs-address command line option.\n\
 ")));
 
-  time (&now);
+  po_current_time (&now);
   timestring = po_strftime (&now);
 
   msgstr = xasprintf ("\
diff --git a/gettext-tools/tests/xgettext-3 b/gettext-tools/tests/xgettext-3
index f5e9f902c..e7981ccc2 100755
--- a/gettext-tools/tests/xgettext-3
+++ b/gettext-tools/tests/xgettext-3
@@ -22,9 +22,10 @@ _("")
 EOF
 
 : ${XGETTEXT=xgettext}
-# delete POT-Creation-Date: line because the date depends on local time.
-${XGETTEXT} --output xg-test3.tmp --no-location -k_ xg-test3.in.c 2>/dev/null || Exit 1
-sed -e '/\"POT-Creation-Date:.*/d' < xg-test3.tmp | LC_ALL=C tr -d '\r' > xg-test3.po
+# Use a fixed timestamp for POT-Creation-Date.
+SOURCE_DATE_EPOCH=1 ${XGETTEXT} --output xg-test3.tmp --no-location -k_ \
+  xg-test3.in.c 2>/dev/null || Exit 1
+LC_ALL=C tr -d '\r' < xg-test3.tmp > xg-test3.po
 
 cat <<EOF > xg-test3.ok
 # SOME DESCRIPTIVE TITLE.
@@ -37,6 +38,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 1970-01-01 00:00+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
-- 
2.29.2

