You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by ka...@apache.org on 2010/07/23 09:57:25 UTC

svn commit: r967000 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/util/CheapDateFormatter.java testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java testing/org/apache/derbyTesting/unitTests/junit/_Suite.java

Author: kahatlen
Date: Fri Jul 23 07:57:25 2010
New Revision: 967000

URL: http://svn.apache.org/viewvc?rev=967000&view=rev
Log:
DERBY-4752: CheapDateFormatter returns incorrect and invalid date strings

Use java.util.Calendar to get the calculations right.

Added:
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java   (with props)
Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java?rev=967000&r1=966999&r2=967000&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/CheapDateFormatter.java Fri Jul 23 07:57:25 2010
@@ -21,29 +21,22 @@
 
 package org.apache.derby.iapi.util;
 
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
 /**
  * This class contains static methods for formatting dates into Strings.
  * It can be used where standard Date formatting is judged to be too
  * expensive.
  */
 public class CheapDateFormatter {
-	static final long SECONDS  = 1000L;
-	static final long MINUTES = SECONDS * 60L;
-	static final long HOURS = MINUTES * 60L;
-	static final long DAYS = HOURS * 24L;
-	static final long NORMAL_YEAR = DAYS * 365L;
-	static final long LEAP_YEAR = NORMAL_YEAR + DAYS;
-	static final long FOURYEARS = (NORMAL_YEAR * 3L) + LEAP_YEAR;
-	static final long END_OF_FIRST_YEAR = NORMAL_YEAR;
-	static final long END_OF_SECOND_YEAR = END_OF_FIRST_YEAR + LEAP_YEAR;
-	static final long END_OF_THIRD_YEAR = END_OF_SECOND_YEAR + NORMAL_YEAR;
-	static final int[] DAYS_IN_MONTH = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-	static final int FEBRUARY = 1;
+    private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
 
 	/**
 	 * This method formats the current date into a String. The input is
 	 * a long representing the number of milliseconds since Jan. 1, 1970.
-	 * The output is a String in the form yyyy/mm/dd hh:mm:ss.ddd GMT.
+	 * The output is a String in the form yyyy-mm-dd hh:mm:ss.ddd GMT.
 	 *
 	 * The purpose of this class is to format date strings without paying
 	 * the price of instantiating ResourceBundles and Locales, which the
@@ -57,97 +50,21 @@ public class CheapDateFormatter {
 	 *
 	 * @param time	The current time in milliseconds since Jan. 1, 1970
 	 *
-	 * @return The date formatted as yyyy/mm/dd hh:mm:ss.ddd GMT.
+	 * @return The date formatted as yyyy-mm-dd hh:mm:ss.ddd GMT.
 	 */
 	public static String formatDate(long time) {
-		// Assume not a leap year until we know otherwise
-		boolean leapYear = false;
-
-		// How many four year periods since Jan. 1, 1970?
-		long year = ((time / FOURYEARS) * 4L);
-
-		// How much time is left over after the four-year periods?
-		long leftover = time % FOURYEARS;
-		time -= (year / 4L) * FOURYEARS;
-
-		year += 1970L;
-
-		// Does time extend past end of first year in four-year period?
-		if (leftover >= END_OF_FIRST_YEAR) {
-			year++;
-			time -= NORMAL_YEAR;
-		}
-
-		// Does time extend past end of second year in four-year period?
-		if (leftover >= END_OF_SECOND_YEAR) {
-			year++;
-			time -= NORMAL_YEAR;
-		}
-
-		// Does time extend past end of third year in four-year period?
-		if (leftover >= END_OF_THIRD_YEAR) {
-			year++;
-			time -= LEAP_YEAR;
-		}
-
-		// It's a leap year if divisible by 4, unless divisible by 100,
-		// unless divisible by 400.
-		if ((year % 4L) == 0) {
-			if ((year % 100L) == 0) {
-				if ((year % 400L) == 0) {
-					leapYear = true;
-				}
-			}
-			leapYear = true;
-		}
-
-		// What day of the year is this, starting at 1?
-		long days = (time / DAYS) + 1;
-
-		// What month is this, starting at 1?
-		int month = 1;
-		for (int i = 0; i < DAYS_IN_MONTH.length; i++) {
-			int daysInMonth;
-
-			if (leapYear && (i == FEBRUARY)) {
-				// February has 29 days in a leap year
-				daysInMonth = 29;
-			} else {
-				// Get number of days in next month
-				daysInMonth = DAYS_IN_MONTH[i];
-			}
-
-			// Is date after the month we are looking at?
-			if (days > daysInMonth) {
-				// Count number of months
-				month++;
-
-				// Subtract number of days in month
-				days -= daysInMonth;
-			} else {
-				// Don't bother to look any more - the date is within
-				// the current month.
-				break;
-			}
-		}
-
-		// How much time is left after days are accounted for?
-		time %= DAYS;
-
-		long hours = time / HOURS;
-
-		// How much time is left after hours are accounted for?
-		time %= HOURS;
-
-		long minutes = time / MINUTES;
-
-		// How much time is left after minutes are accounted for?
-		time %= MINUTES;
-
-		long seconds = time / SECONDS;
-
-		// How much time is left after seconds are accounted for?
-		time %= SECONDS;
+        // Get a GMT calendar with a well-known locale to help us calculate
+        // the components of the date.
+        Calendar cal = Calendar.getInstance(GMT, Locale.US);
+        cal.setTimeInMillis(time);
+
+        int year = cal.get(Calendar.YEAR);
+        int month = cal.get(Calendar.MONTH) + 1; // convert 0-based to 1-based
+        int days = cal.get(Calendar.DAY_OF_MONTH);
+        int hours = cal.get(Calendar.HOUR_OF_DAY);
+        int minutes = cal.get(Calendar.MINUTE);
+        int seconds = cal.get(Calendar.SECOND);
+        int millis = cal.get(Calendar.MILLISECOND);
 
 		return year + "-" +
 				twoDigits(month) + "-" +
@@ -155,22 +72,22 @@ public class CheapDateFormatter {
 				twoDigits(hours) + ":" +
 				twoDigits(minutes) + ":" +
 				twoDigits(seconds) + "." +
-				threeDigits(time) + " GMT";
+				threeDigits(millis) + " GMT";
 	}
 
-	private static String twoDigits(long val) {
+	private static String twoDigits(int val) {
 		String retval;
 
 		if (val < 10) {
 			retval = "0" + val;
 		} else {
-			retval = Long.toString(val);
+			retval = Integer.toString(val);
 		}
 
 		return retval;
 	}
 
-	private static String threeDigits(long val) {
+	private static String threeDigits(int val) {
 		String retval;
 
 		if (val < 10) {
@@ -178,7 +95,7 @@ public class CheapDateFormatter {
 		} else if (val < 100) {
 			retval = "0" + val;
 		} else {
-			retval = Long.toString(val);
+			retval = Integer.toString(val);
 		}
 
 		return retval;

Added: db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java?rev=967000&view=auto
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java (added)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java Fri Jul 23 07:57:25 2010
@@ -0,0 +1,94 @@
+/*
+
+   Derby - Class org.apache.derbyTesting.unitTests.junit.CheapDateFormatterTest
+
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to you under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+ */
+
+package org.apache.derbyTesting.unitTests.junit;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import junit.framework.Test;
+import junit.framework.TestSuite;
+import org.apache.derby.iapi.util.CheapDateFormatter;
+import org.apache.derbyTesting.junit.BaseTestCase;
+
+/**
+ * Unit tests for the CheapDateFormatter class.
+ */
+public class CheapDateFormatterTest extends BaseTestCase {
+    public CheapDateFormatterTest(String name) {
+        super(name);
+    }
+
+    public static Test suite() {
+        return new TestSuite(CheapDateFormatterTest.class);
+    }
+
+    /**
+     * Tests for the {@code formatDate()} method.
+     */
+    public void testFormatDate() throws ParseException {
+        assertDateString("1970-01-01 00:00:00.000 GMT"); // Epoch
+
+        // DERBY-4752: Times the first day in a leap year used to be
+        // formatted with month 13 in the previous year. Verify that this
+        // works correctly for the first day of the leap year 2016.
+        assertDateString("2015-12-31 23:59:59.999 GMT");
+        assertDateString("2016-01-01 00:00:00.000 GMT");
+        assertDateString("2016-01-01 00:00:00.001 GMT");
+        assertDateString("2016-01-01 08:31:23.456 GMT");
+        assertDateString("2016-01-02 12:00:00.000 GMT");
+
+        // DERBY-4752: We used to get a one day skew each time we passed a
+        // year divisible by four that was not a leap year (like 2100, 2200,
+        // 2300, 2500, ...).
+        assertDateString("2100-05-17 14:10:44.701 GMT");
+        assertDateString("2927-06-07 00:00:00.000 GMT");
+        assertDateString("9999-12-31 23:59:59.999 GMT");
+
+        // DERBY-4752: Years divisible by 100, but not by 400, are not leap
+        // years. Still, formatDate() used to return February 29 for the
+        // following dates:
+        assertDateString("2100-03-01 12:00:00.000 GMT");
+        assertDateString("2200-03-02 12:00:00.000 GMT");
+        assertDateString("2300-03-03 12:00:00.000 GMT");
+        assertDateString("2500-03-04 12:00:00.000 GMT");
+
+        // Year 8000 will be a leap year, unless a better calendar system
+        // has been devised by then.
+        assertDateString("8000-02-28 12:00:00.000 GMT");
+        assertDateString("8000-02-29 12:00:00.000 GMT");
+        assertDateString("8000-01-03 12:00:00.000 GMT");
+    }
+
+    /**
+     * Convert a date string to a long representing milliseconds since Epoch,
+     * feed that value to CheapDateFormatter.formatDate(), and verify that
+     * the exact same date string is returned.
+     *
+     * @param date a string representing the date to test
+     * @throws ParseException if the date string cannot be parsed
+     */
+    private void assertDateString(String date) throws ParseException {
+        DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS z");
+        long time = df.parse(date).getTime();
+        assertEquals(date, CheapDateFormatter.formatDate(time));
+    }
+}

Propchange: db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/CheapDateFormatterTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java?rev=967000&r1=966999&r2=967000&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/unitTests/junit/_Suite.java Fri Jul 23 07:57:25 2010
@@ -47,6 +47,7 @@ public class _Suite extends BaseTestCase
         TestSuite suite = new TestSuite("JUnit unit tests");
 
         suite.addTest(ArrayInputStreamTest.suite());
+        suite.addTest(CheapDateFormatterTest.suite());
         suite.addTest(FormatableBitSetTest.suite());
         suite.addTest(SystemPrivilegesPermissionTest.suite());
         suite.addTest(UTF8UtilTest.suite());