[PATCH] Extend time parsing to before 1970

Linus Torvalds torvalds at linux-foundation.org
Thu Apr 28 16:17:15 PDT 2016


From: Linus Torvalds <torvalds at linux-foundation.org>
Date: Thu, 28 Apr 2016 15:13:30 -0700
Subject: [PATCH] Extend time parsing to before 1970

It turns out that we are starting to have users that have logs that go
back that far. It won't be common, but let's get it right anyway.

NOTE! With us now supporting dates earlier in 1900, this also makes
"utc_mktime()" always add the "1900" to the year field.  That way we
avoid ever using the fairly ambiguous two-digit shorthand.

It didn't use to be all that ambiguous when we knew that any two-digit
number less than 70 had to be 2000+.  Now that we support going back to
earlier in the last centiry, that certainty is eroding.

Signed-off-by: Linus Torvalds <torvalds at linux-foundation.org>
---

This looks reasonable, but has a big problem: as Robert noticed, Qt seems 
to have some very very dubious time handling. Using "uint" for a timestamp 
is very very wrong.

So what this patch results in from my testing is that

 - yes, you can have dives in 1968 or earlier, and subsurface will load 
   and save them correctly

 - the QT UI elements screw it up completely

For example, when I modify our test0 dive to be at Jan 1, 1968, in the 
divelist I see exactly that: "Mon Jan 1, 1968 7:00AM". I see that in the 
"Info" tab for the date too. Because we handle all the dates properly 
as 64-bit entities, and this patch makes utc_mkdate/utc_mktime do it right 
for those dates.

But in the main "Notes" tab, which has the editable Date/Time slots, show 
the date and time for that dive as

  Thu Feb 7, 2104 1:28PM

because it ends up doing the time as a unsigned int.

That's very odd. It's *especially* odd since I'm doing this on x86-64, 
which natively has a 64-bit signed time_t, and all the normal date 
handling functions should just work fine. But Qt seems to have explicitly 
screwed up here.

Thiago?

Looking at 

   http://doc.qt.io/qt-5/qdatetime.html#fromTime_t-1

it just looks like "QDateTime::fromTime_t()" is completely and utterly 
broken.

I guess we could replace all those with

   QDateTime::fromMSecsSinceEpoch(when*1000, Qt::UTC)

or something. I have *not* tested whether that works. 




 core/divelist.c         |  4 +--
 core/load-git.c         |  6 ++---
 core/planner.c          |  2 +-
 core/save-git.c         |  8 +++---
 core/save-html.c        |  2 +-
 core/save-xml.c         |  2 +-
 core/statistics.c       |  6 ++---
 core/time.c             | 72 ++++++++++++++++++++++++++++++++++++-------------
 core/uemis-downloader.c |  1 -
 9 files changed, 69 insertions(+), 34 deletions(-)

diff --git a/core/divelist.c b/core/divelist.c
index 52545af1a945..1ac19bd3f9e8 100644
--- a/core/divelist.c
+++ b/core/divelist.c
@@ -504,7 +504,7 @@ void dump_trip_list(void)
 		printf("%s trip %d to \"%s\" on %04u-%02u-%02u %02u:%02u:%02u (%d dives - %p)\n",
 		       trip->autogen ? "autogen " : "",
 		       ++i, trip->location,
-		       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+		       tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
 		       trip->nrdives, trip);
 		last_time = trip->when;
 	}
@@ -531,7 +531,7 @@ dive_trip_t *find_matching_trip(timestamp_t when)
 		utc_mkdate(trip->when, &tm);
 		printf("found trip %p @ %04d-%02d-%02d %02d:%02d:%02d\n",
 		       trip,
-		       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+		       tm.tm_year, tm.tm_mon + 1, tm.tm_mday,
 		       tm.tm_hour, tm.tm_min, tm.tm_sec);
 	}
 #endif
diff --git a/core/load-git.c b/core/load-git.c
index 339621c63909..be7fac7d84ae 100644
--- a/core/load-git.c
+++ b/core/load-git.c
@@ -126,7 +126,7 @@ static void update_date(timestamp_t *when, const char *line)
 	if (sscanf(line, "%04u-%02u-%02u", &yyyy, &mm, &dd) != 3)
 		return;
 	utc_mkdate(*when, &tm);
-	tm.tm_year = yyyy - 1900;
+	tm.tm_year = yyyy;
 	tm.tm_mon = mm - 1;
 	tm.tm_mday = dd;
 	*when = utc_mktime(&tm);
@@ -1199,7 +1199,7 @@ static dive_trip_t *create_new_trip(int yyyy, int mm, int dd)
 
 static bool validate_date(int yyyy, int mm, int dd)
 {
-	return yyyy > 1970 && yyyy < 3000 &&
+	return yyyy > 1930 && yyyy < 3000 &&
 		mm > 0 && mm < 13 &&
 		dd > 0 && dd < 32;
 }
@@ -1308,7 +1308,7 @@ static int dive_directory(const char *root, const git_tree_entry *entry, const c
 	tm.tm_hour = h;
 	tm.tm_min = m;
 	tm.tm_sec = s;
-	tm.tm_year = yyyy - 1900;
+	tm.tm_year = yyyy;
 	tm.tm_mon = mm-1;
 	tm.tm_mday = dd;
 
diff --git a/core/planner.c b/core/planner.c
index a58c698abc2d..e67033dd8080 100644
--- a/core/planner.c
+++ b/core/planner.c
@@ -51,7 +51,7 @@ void dump_plan(struct diveplan *diveplan)
 	utc_mkdate(diveplan->when, &tm);
 
 	printf("\nDiveplan @ %04d-%02d-%02d %02d:%02d:%02d (surfpres %dmbar):\n",
-	       tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
+	       tm.tm_year, tm.tm_mon + 1, tm.tm_mday,
 	       tm.tm_hour, tm.tm_min, tm.tm_sec,
 	       diveplan->surface_pressure);
 	dp = diveplan->dp;
diff --git a/core/save-git.c b/core/save-git.c
index 08161f3de661..0620a3f3d1fa 100644
--- a/core/save-git.c
+++ b/core/save-git.c
@@ -215,7 +215,7 @@ static void show_date(struct membuffer *b, timestamp_t when)
 	utc_mkdate(when, &tm);
 
 	put_format(b, "date %04u-%02u-%02u\n",
-		   tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+		   tm.tm_year, tm.tm_mon + 1, tm.tm_mday);
 	put_format(b, "time %02u:%02u:%02u\n",
 		   tm.tm_hour, tm.tm_min, tm.tm_sec);
 }
@@ -535,7 +535,7 @@ static void create_dive_name(struct dive *dive, struct membuffer *name, struct t
 
 	utc_mkdate(dive->when, &tm);
 	if (tm.tm_year != dirtm->tm_year)
-		put_format(name, "%04u-", tm.tm_year + 1900);
+		put_format(name, "%04u-", tm.tm_year);
 	if (tm.tm_mon != dirtm->tm_mon)
 		put_format(name, "%02u-", tm.tm_mon+1);
 
@@ -734,7 +734,7 @@ static int save_trip_description(git_repository *repo, struct dir *dir, dive_tri
 	struct membuffer desc = { 0 };
 
 	put_format(&desc, "date %04u-%02u-%02u\n",
-		   tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
+		   tm->tm_year, tm->tm_mon + 1, tm->tm_mday);
 	put_format(&desc, "time %02u:%02u:%02u\n",
 		   tm->tm_hour, tm->tm_min, tm->tm_sec);
 
@@ -949,7 +949,7 @@ static int create_git_tree(git_repository *repo, struct dir *root, bool select_o
 
 		/* Create the date-based hierarchy */
 		utc_mkdate(trip ? trip->when : dive->when, &tm);
-		tree = mktree(repo, root, "%04d", tm.tm_year + 1900);
+		tree = mktree(repo, root, "%04d", tm.tm_year);
 		tree = mktree(repo, tree, "%02d", tm.tm_mon + 1);
 
 		if (trip) {
diff --git a/core/save-html.c b/core/save-html.c
index 2d0ea9cf359e..4f811558a28d 100644
--- a/core/save-html.c
+++ b/core/save-html.c
@@ -201,7 +201,7 @@ void put_HTML_date(struct membuffer *b, struct dive *dive, const char *pre, cons
 {
 	struct tm tm;
 	utc_mkdate(dive->when, &tm);
-	put_format(b, "%s%04u-%02u-%02u%s", pre, tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, post);
+	put_format(b, "%s%04u-%02u-%02u%s", pre, tm.tm_year, tm.tm_mon + 1, tm.tm_mday, post);
 }
 
 void put_HTML_quoted(struct membuffer *b, const char *text)
diff --git a/core/save-xml.c b/core/save-xml.c
index 80dda63b8ae7..c9b6a5b99930 100644
--- a/core/save-xml.c
+++ b/core/save-xml.c
@@ -323,7 +323,7 @@ static void show_date(struct membuffer *b, timestamp_t when)
 	utc_mkdate(when, &tm);
 
 	put_format(b, " date='%04u-%02u-%02u'",
-		   tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
+		   tm.tm_year, tm.tm_mon + 1, tm.tm_mday);
 	put_format(b, " time='%02u:%02u:%02u'",
 		   tm.tm_hour, tm.tm_min, tm.tm_sec);
 }
diff --git a/core/statistics.c b/core/statistics.c
index 6a05cffc1722..1b9865443d48 100644
--- a/core/statistics.c
+++ b/core/statistics.c
@@ -160,10 +160,10 @@ void process_all_dives(struct dive *dive, struct dive **prev_dive)
 		/* yearly statistics */
 		utc_mkdate(dp->when, &tm);
 		if (current_year == 0)
-			current_year = tm.tm_year + 1900;
+			current_year = tm.tm_year;
 
-		if (current_year != tm.tm_year + 1900) {
-			current_year = tm.tm_year + 1900;
+		if (current_year != tm.tm_year) {
+			current_year = tm.tm_year;
 			process_dive(dp, &(stats_yearly[++year_iter]));
 			stats_yearly[year_iter].is_year = true;
 		} else {
diff --git a/core/time.c b/core/time.c
index 0893f19d81b9..9992658b6553 100644
--- a/core/time.c
+++ b/core/time.c
@@ -2,6 +2,24 @@
 #include "dive.h"
 
 /*
+ * The date handling internally works in seconds since
+ * Jan 1, 1900. That avoids negative numbers which avoids
+ * some silly problems.
+ *
+ * But we then use the same base epoch base (Jan 1, 1970)
+ * that POSIX uses, so that we can use the normal date
+ * handling functions for getting current time etc.
+ *
+ * There's 25567 dats from Jan 1, 1900 to Jan 1, 1970.
+ *
+ * NOTE! The SEC_PER_DAY is not so much because the
+ * number is complicated, as to make sure we always
+ * expand the type to "timestamp_t" in the arithmetic.
+ */
+#define SEC_PER_DAY  ((timestamp_t) 24*60*60)
+#define EPOCH_OFFSET (25567 * SEC_PER_DAY)
+
+/*
  * Convert 64-bit timestamp to 'struct tm' in UTC.
  *
  * On 32-bit machines, only do 64-bit arithmetic for the seconds
@@ -24,7 +42,14 @@ void utc_mkdate(timestamp_t timestamp, struct tm *tm)
 
 	memset(tm, 0, sizeof(*tm));
 
-	/* seconds since 1970 -> minutes since 1970 */
+	// Midnight at Jan 1, 1970 means "no date"
+	if (!timestamp)
+		return;
+
+	/* Convert to seconds since 1900 */
+	timestamp += EPOCH_OFFSET;
+
+	/* minutes since 1900 */
 	tm->tm_sec = timestamp % 60;
 	val = timestamp /= 60;
 
@@ -34,19 +59,21 @@ void utc_mkdate(timestamp_t timestamp, struct tm *tm)
 	tm->tm_hour = val % 24;
 	val /= 24;
 
-	/* Jan 1, 1970 was a Thursday (tm_wday=4) */
-	tm->tm_wday = (val + 4) % 7;
+	/* Jan 1, 1900 was a Monday (tm_wday=1) */
+	tm->tm_wday = (val + 1) % 7;
 
 	/*
-	 * Now we're in "days since Jan 1, 1970". To make things easier,
-	 * let's make it "days since Jan 1, 1968", since that's a leap-year
+	 * Now we're in "days since Jan 1, 1900". To make things easier,
+	 * let's make it "days since Jan 1, 1904", since that's a leap-year.
+	 * 1900 itself was not. The following logic will get 1900-1903
+	 * wrong. If you were diving back then, you're kind of screwed.
 	 */
-	val += 365 + 366;
+	val -= 365*4;
 
 	/* This only works up until 2099 (2100 isn't a leap-year) */
 	leapyears = val / (365 * 4 + 1);
 	val %= (365 * 4 + 1);
-	tm->tm_year = 68 + leapyears * 4;
+	tm->tm_year = 1904 + leapyears * 4;
 
 	/* Handle the leap-year itself */
 	mp = mdays_leap;
@@ -75,24 +102,33 @@ timestamp_t utc_mktime(struct tm *tm)
 	int year = tm->tm_year;
 	int month = tm->tm_mon;
 	int day = tm->tm_mday;
+	int days_since_1900;
+	timestamp_t when;
 
 	/* First normalize relative to 1900 */
-	if (year < 70)
+	if (year < 50)
 		year += 100;
 	else if (year > 1900)
 		year -= 1900;
 
-	/* Normalized to Jan 1, 1970: unix time */
-	year -= 70;
-
-	if (year < 0 || year > 129) /* algo only works for 1970-2099 */
-		return -1;
+	if (year < 0 || year > 129) /* algo only works for 1900-2099 */
+		return 0;
 	if (month < 0 || month > 11) /* array bounds */
-		return -1;
-	if (month < 2 || (year + 2) % 4)
+		return 0;
+	if (month < 2 || (year && year % 4))
 		day--;
 	if (tm->tm_hour < 0 || tm->tm_min < 0 || tm->tm_sec < 0)
-		return -1;
-	return (year * 365 + (year + 1) / 4 + mdays[month] + day) * 24 * 60 * 60UL +
-	       tm->tm_hour * 60 * 60 + tm->tm_min * 60 + tm->tm_sec;
+		return 0;
+
+	/* This works until 2099 */
+	days_since_1900 = year * 365 + (year - 1) / 4;
+
+	/* Note the 'day' fixup for non-leapyears above */
+	days_since_1900 += mdays[month] + day;
+
+	/* Now add it all up, making sure to do this part in "timestamp_t" */
+	when = days_since_1900 * SEC_PER_DAY;
+	when += tm->tm_hour * 60 * 60 + tm->tm_min * 60 + tm->tm_sec;
+
+	return when - EPOCH_OFFSET;
 }
diff --git a/core/uemis-downloader.c b/core/uemis-downloader.c
index b9b532303440..925783a6d8d9 100644
--- a/core/uemis-downloader.c
+++ b/core/uemis-downloader.c
@@ -86,7 +86,6 @@ static void uemis_ts(char *buffer, void *_when)
 	       &tm.tm_year, &tm.tm_mon, &tm.tm_mday,
 	       &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
 	tm.tm_mon -= 1;
-	tm.tm_year -= 1900;
 	*when = utc_mktime(&tm);
 }
 
-- 
2.8.1.280.ga0c4ddf



More information about the subsurface mailing list