Module Name:    src
Committed By:   martin
Date:           Sat Sep  6 18:04:28 UTC 2014

Modified Files:
        src/sys/dev: clock_subr.c

Log Message:
Counting leap years was fine while we had 32bit time_t - but now it
is not a good idea for dates far away in the future.
For dates in the year 2000 or later, use arithmetic instead (since the
repeating periods are well aligned). Should fix PR 49144.


To generate a diff of this commit:
cvs rdiff -u -r1.20 -r1.21 src/sys/dev/clock_subr.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/dev/clock_subr.c
diff -u src/sys/dev/clock_subr.c:1.20 src/sys/dev/clock_subr.c:1.21
--- src/sys/dev/clock_subr.c:1.20	Mon Sep  1 12:25:52 2014
+++ src/sys/dev/clock_subr.c	Sat Sep  6 18:04:28 2014
@@ -1,4 +1,4 @@
-/*	$NetBSD: clock_subr.c,v 1.20 2014/09/01 12:25:52 apb Exp $	*/
+/*	$NetBSD: clock_subr.c,v 1.21 2014/09/06 18:04:28 martin Exp $	*/
 
 /*
  * Copyright (c) 1988 University of Utah.
@@ -50,7 +50,7 @@
 
 #ifdef _KERNEL
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: clock_subr.c,v 1.20 2014/09/01 12:25:52 apb Exp $");
+__KERNEL_RCSID(0, "$NetBSD: clock_subr.c,v 1.21 2014/09/06 18:04:28 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -66,6 +66,19 @@ static inline int leapyear(int year);
 #define	days_in_year(a) 	(leapyear(a) ? 366 : 365)
 #define	days_in_month(a) 	(month_days[(a) - 1])
 
+/* for easier alignment:
+ * time from the epoch to 2000 (there were 7 leap years): */
+#define	DAYSTO2000	(365*30+7)
+
+/* 4 year intervals include 1 leap year */
+#define	DAYS4YEARS	(365*4+1)
+
+/* 100 year intervals include 24 leap years */
+#define	DAYS100YEARS	(365*100+24)
+
+/* 400 year intervals include 97 leap years */
+#define	DAYS400YEARS	(365*400+97)
+
 static const int month_days[12] = {
 	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
 };
@@ -109,11 +122,35 @@ clock_ymdhms_to_secs(struct clock_ymdhms
 	if (year < POSIX_BASE_YEAR)
 		return -1;
 	days = 0;
-	for (i = POSIX_BASE_YEAR; i < year; i++)
-		days += days_in_year(i);
 	if (leapyear(year) && dt->dt_mon > FEBRUARY)
 		days++;
 
+	if (year < 2000) {
+		/* simple way for early years */
+		for (i = POSIX_BASE_YEAR; i < year; i++)
+			days += days_in_year(i);
+	} else {
+		/* years are properly aligned */
+		days += DAYSTO2000;
+		year -= 2000;
+
+		i = year / 400;
+		days += i * DAYS400YEARS;
+		year -= i * 400;
+
+		i = year / 100;
+		days += i * DAYS100YEARS;
+		year -= i * 100;
+
+		i = year / 4;
+		days += i * DAYS4YEARS;
+		year -= i * 4;
+
+		for (i = dt->dt_year-year; i < dt->dt_year; i++)
+			days += days_in_year(i);
+	}
+
+
 	/* Months */
 	for (i = 1; i < dt->dt_mon; i++)
 	  	days += days_in_month(i);
@@ -133,35 +170,50 @@ clock_ymdhms_to_secs(struct clock_ymdhms
 void
 clock_secs_to_ymdhms(time_t secs, struct clock_ymdhms *dt)
 {
-	int mthdays[12];
-	int i;
+	int i, leap;
 	time_t days;
 	time_t rsec;	/* remainder seconds */
 
-	/*
-	 * This function uses a local copy of month_days[]
-	 * so the copy can be modified (and thread-safe).
-	 * See the definition of days_in_month() above.
-	 */
-	memcpy(mthdays, month_days, sizeof(mthdays));
-#define month_days mthdays
-
 	days = secs / SECDAY;
 	rsec = secs % SECDAY;
 
 	/* Day of week (Note: 1/1/1970 was a Thursday) */
 	dt->dt_wday = (days + 4) % 7;
 
-	/* Subtract out whole years, counting them in i. */
-	for (i = POSIX_BASE_YEAR; days >= days_in_year(i); i++)
-		days -= days_in_year(i);
-	dt->dt_year = i;
+	if (days >= DAYSTO2000) {
+		days -= DAYSTO2000;
+		dt->dt_year = 2000;
+
+		i = days / DAYS400YEARS;
+		days -= i*DAYS400YEARS;
+		dt->dt_year += i*400;
+
+		i = days / DAYS100YEARS;
+		days -= i*DAYS100YEARS;
+		dt->dt_year += i*100;
+
+		i = days / DAYS4YEARS;
+		days -= i*DAYS4YEARS;
+		dt->dt_year += i*4;
+
+		for (i = dt->dt_year; days >= days_in_year(i); i++)
+			days -= days_in_year(i);
+		dt->dt_year = i;
+	} else {
+		/* Subtract out whole years, counting them in i. */
+		for (i = POSIX_BASE_YEAR; days >= days_in_year(i); i++)
+			days -= days_in_year(i);
+		dt->dt_year = i;
+	}
 
 	/* Subtract out whole months, counting them in i. */
-	if (leapyear(i))
-		days_in_month(FEBRUARY) = 29;
-	for (i = 1; days >= days_in_month(i); i++)
-		days -= days_in_month(i);
+	for (leap = 0, i = 1; days >= days_in_month(i)+leap; i++) {
+		days -= days_in_month(i)+leap;
+		if (i == 1 && leapyear(dt->dt_year))
+			leap = 1;
+		else
+			leap = 0;
+	}
 	dt->dt_mon = i;
 
 	/* Days are what is left over (+1) from all that. */
@@ -173,5 +225,4 @@ clock_secs_to_ymdhms(time_t secs, struct
 	dt->dt_min  = rsec / 60;
 	rsec = rsec % 60;
 	dt->dt_sec  = rsec;
-#undef month_days
 }

Reply via email to