[Mulgara-svn] r974 - trunk/src/jar/util/java/org/mulgara/util
pag at mulgara.org
pag at mulgara.org
Fri Jun 6 04:03:07 UTC 2008
Author: pag
Date: 2008-06-05 21:03:07 -0700 (Thu, 05 Jun 2008)
New Revision: 974
Added:
trunk/src/jar/util/java/org/mulgara/util/LexicalDateTime.java
trunk/src/jar/util/java/org/mulgara/util/LexicalDateTimeUnitTest.java
trunk/src/jar/util/java/org/mulgara/util/Timezone.java
Log:
Utilities for converting the lexical form of XSD dateTimes into bytes appropriate for storing, while not canonicalizing the data, and retaining full lexical information.
Added: trunk/src/jar/util/java/org/mulgara/util/LexicalDateTime.java
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/LexicalDateTime.java (rev 0)
+++ trunk/src/jar/util/java/org/mulgara/util/LexicalDateTime.java 2008-06-06 04:03:07 UTC (rev 974)
@@ -0,0 +1,444 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.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.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.util;
+
+import java.text.ParseException;
+import java.nio.ByteBuffer;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeZone;
+import org.joda.time.format.DateTimeFormat;
+import org.joda.time.format.DateTimeFormatter;
+import static org.joda.time.DateTimeZone.UTC;
+
+/**
+ * This class represents a dateTime value, preserving its lexical representation exactly.
+ * It stores the value of the dateTime in the canonical form, but also contains values which
+ * allow the preservation of the non-canonical format.
+ *
+ * @created Jun 5, 2008
+ * @author Paul Gearon
+ * @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class LexicalDateTime {
+
+ /** The character for separating date elements */
+ private static final char DATE_SEPARATOR = '-';
+
+ /** The character for separating the date part from the time part */
+ private static final char DATE_TIME_SEPARATOR = 'T';
+
+ /** The character for separating time elements */
+ private static final char TIME_SEPARATOR = ':';
+
+ /** The character for separating the milliseconds from the seconds */
+ private static final char MILLI_SEPARATOR = '.';
+
+ /** The string form for the character separating the milliseconds from the seconds */
+ private static final String MILLI_SEPARATOR_STR = ".";
+
+ /** The character for indicating the UTC timezone (Zulu time). */
+ private static final char ZULU = 'Z';
+
+ /** The string form for the character indicating the UTC timezone (Zulu time). */
+ private static final String ZULU_STR = "Z";
+
+ /** The character for indicating a positive timezone offset */
+ private static final char POS_TZ = '+';
+
+ /** The character for indicating a negative timezone offset */
+ private static final char NEG_TZ = '-';
+
+ /** The hour value for midnight */
+ private static final int MIDNIGHT = 24;
+
+ /** The string representation of midnight when the midnight flag is set */
+ private static final String MIDNIGHT_STR = "24:00:00";
+
+ /** Standard start of parsing error messages */
+ private static final String BAD_FORMAT = "Bad format in ";
+
+ /** Output format for the dateTime */
+ private static final String LEXICAL_PATTERN = "yyyy-MM-dd'T'HH:mm:ss";
+
+ /** Output format for the date portion of the dateTime */
+ private static final String SHORT_PATTERN = "yyyy-MM-dd'T'";
+
+ /** The formatter used for converting the dateTime into a lexical form */
+ private static final DateTimeFormatter MAIN_FORMATTER = DateTimeFormat.forPattern(LEXICAL_PATTERN);
+
+ /** A supplemantary formatter for outputting the date, when the time has to be represented in non-canonical form */
+ private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormat.forPattern(SHORT_PATTERN);
+
+ /** The number of milliseconds in a second */
+ private static final int MILLIS = 1000;
+
+ /** The number of milliseconds in a minute */
+ private static final int MILLIS_IN_MINUTE = MILLIS * 60;
+
+ /** The number of milliseconds in an hour */
+ private static final long MILLIS_IN_HOUR = MILLIS_IN_MINUTE * 60;
+
+ /** The bit used to encode the localFlag */
+ private static final byte LOCAL_BIT = 0x02;
+
+ /** The bit used to encode the midnightFlag */
+ private static final byte MIDNIGHT_BIT = 0x01;
+
+ /** The mask for the timezone bits */
+ private static final byte TZ_MASK = (byte)0xFC;
+
+ /** Number of bytes in a Long */
+ private static final int SIZEOF_LONG = Long.SIZE / 8;
+
+ /** The offset of the timezone data in an encoded buffer */
+ private static final int TZ_OFFSET = SIZEOF_LONG;
+
+ /** The offset of the fractional seconds decimal places in an encoded buffer */
+ private static final int PLACES_OFFSET = TZ_OFFSET + 1;
+
+ /** The milliseconds since the epoch */
+ private final long millis;
+
+ /** The hours offset for the time */
+ private final int tzHours;
+
+ /** The minutes offset for the timezone. A multiple of 15. */
+ private final int tzMinutes;
+
+ /** Indicates that the time was supplied as 24:00:00. */
+ private final boolean midnight;
+
+ /** The number of decimal places used to represent the milliseconds. No greater than 3. */
+ private final byte milliPlaces;
+
+ /** Indicates no supplied timezone. This defaults to the local timezone. */
+ private final boolean localFlag;
+
+ /** Indicates ZULU time, which is equivalent to +00:00. */
+ private final boolean zuluFlag;
+
+ /** A DateTime corresponding to this object. Only created if needed. */
+ private DateTime cachedDateTime = null;
+
+ /**
+ * This constructor is used to set each field explicitly, when all such information is available.
+ * No checking is performed on the consistency of the millisecond value, though some minimal testing is done on flags.
+ * Whether tested or not, the following should hold:
+ * <ul>
+ * <li>if <code>isMidnight</code> is set, then <code>millis</code> must be a multiple of 24 hours.</li>
+ * <li>if <code>isLocalTz</code> is set, then <code>tzHours</code> and <code>tzMinutes</code> should be 0,
+ * and <code>isZulu</code> must be false.</li>
+ * <li>if <code>isZulu</code> is set, then <code>tzHours</code> and <code>tzMinutes</code> must be 0.</li>
+ * <li><code>millis</code> % 10^(6 - milliPlaces) == 0</li>
+ * </ul>
+ * @param millis The milliseconds since the epoch.
+ * @param tzHours The hour offset for the timezone.
+ * @param tzMinutes The minute offset for the timezone.
+ * @param isMidnight If the non-canonical form for midnight is used. "24:00:00"
+ * @param milliPlaces The number of decimal places used for representing millisecds as fractions of a second.
+ * @param isLocalTz Indicates no timezone information, so use the local default.
+ * @param isZulu Indicates that the timezone is "Zulu". This is equivalent to 00:00 and is represented as "Z".
+ * @throws IllegalArgumentException if the <code>isZulu</code> flag conflicts with the timezone values or the offsets.
+ */
+ public LexicalDateTime(long millis, int tzHours, int tzMinutes, boolean isMidnight, byte milliPlaces, boolean isLocalTz, boolean isZulu) {
+ this.millis = millis;
+ this.tzHours = tzHours;
+ this.tzMinutes = tzMinutes;
+ this.midnight = isMidnight;
+ this.milliPlaces = milliPlaces;
+ this.localFlag = isLocalTz;
+ this.zuluFlag = isZulu;
+ testTimezoneConsistency();
+ }
+
+ /**
+ * Internal constructor used with a dateTime object, and parsed fields.
+ * No checking is performed on the consistency of the millisecond value, though some minimal testing is done on flags.
+ * Whether tested or not, the following should hold:
+ * <ul>
+ * <li>if <code>isMidnight</code> is set, then <code>millis</code> plus the timezone offset must be a multiple
+ * of 24 hours.</li>
+ * <li>if <code>isLocalTz</code> is set, then <code>tzHours</code> and <code>tzMinutes</code> should be 0,
+ * and <code>isZulu</code> must be false.</li>
+ * <li>if <code>isZulu</code> is set, then <code>tzHours</code> and <code>tzMinutes</code> must be 0.</li>
+ * <li><code>millis</code> % 10^(3 - milliPlaces) == 0</li>
+ * <li>The <code>tzHours</code> and <code>tzMinutes</code> values should correspond to the values in the
+ * <code>dateTime</code> field.</li>
+ * </ul>
+ * @param dateTime The dateTime object representing the time.
+ * @param tzHours The hour offset for the timezone.
+ * @param tzMinutes The minute offset for the timezone.
+ * @param isMidnight If the non-canonical form for midnight is used. "24:00:00"
+ * @param milliPlaces The number of decimal places used for representing millisecds as fractions of a second.
+ * @param isLocalTz Indicates no timezone information, so use the local default.
+ * @param isZulu Indicates that the timezone is "Zulu". This is equivalent to 00:00 and is represented as "Z".
+ * @throws IllegalArgumentException if the <code>isZulu</code> flag conflicts with the timezone values or the offsets.
+ */
+ private LexicalDateTime(DateTime dateTime, int tzHours, int tzMinutes, boolean isMidnight, byte milliPlaces, boolean isLocalTz, boolean isZulu) {
+ this.millis = dateTime.getMillis();
+ this.tzHours = tzHours;
+ this.tzMinutes = tzMinutes;
+ this.midnight = isMidnight;
+ this.cachedDateTime = dateTime;
+ this.milliPlaces = milliPlaces;
+ this.localFlag = isLocalTz;
+ this.zuluFlag = isZulu;
+ testTimezoneConsistency();
+ }
+
+ /**
+ * Convenience constructor which allows easy construction of a LexicalDateTime using the milliseconds since the epoch.
+ * @param millis Milliseconds since the epoch.
+ */
+ public LexicalDateTime(long millis) {
+ this.millis = millis;
+ long offset = DateTimeZone.getDefault().getOffset(0);
+ tzHours = (int)(offset / MILLIS_IN_HOUR);
+ tzMinutes = (int)(offset % MILLIS_IN_HOUR) / MILLIS_IN_MINUTE;
+ midnight = false;
+ cachedDateTime = null;
+ milliPlaces = 0;
+ localFlag = true;
+ zuluFlag = false;
+ }
+
+ /** Gets the number of milliseconds since the epoch. */
+ public long getMillis() {
+ return millis;
+ }
+
+ /** The the hour part of the offset for the timezone. */
+ public int getTZHour() {
+ return tzHours;
+ }
+
+ /** The the minute part of the offset for the timezone. */
+ public long getTZMinute() {
+ return tzMinutes;
+ }
+
+ /** Gets the flag that indicates that this time is a non-canonical form of midnight. */
+ public boolean isMidnight() {
+ return midnight;
+ }
+
+ /** Gets the flag that indicates no timezone is present, and the local default should be used. */
+ public boolean isLocal() {
+ return localFlag;
+ }
+
+ /** Gets the flag that indicates the Zulu timezone (UTC) and representation. */
+ public boolean isZulu() {
+ return zuluFlag;
+ }
+
+ /** Gets the number of decimal places to represent the fraction of a second. */
+ public byte getDecimalPlaces() {
+ return milliPlaces;
+ }
+
+ /**
+ * Fills in a ByteBuffer with the data required to encode this object.
+ * @param bb The {@link java.nio.ByteBuffer} to populate.
+ * @return The populated ByteBuffer.
+ */
+ public ByteBuffer encode(ByteBuffer bb) {
+ assert bb.limit() >= PLACES_OFFSET;
+ bb.putLong(0, millis);
+ bb.put(TZ_OFFSET, encodeTimezoneState());
+ bb.put(PLACES_OFFSET, milliPlaces);
+ return bb;
+ }
+
+ /**
+ * Creates a byte code for the timezone and flags of this dateTime.
+ * <table>
+ * <tr><td>bits 7-2</td><td>timezone code</td></tr>
+ * <tr><td >bit 1</td><td>local flag</td></tr>
+ * <tr><td>bit 0</td><td>midnight flag</td></tr>
+ * </table>
+ * @return a byte containing the timezone data.
+ */
+ public byte encodeTimezoneState() {
+ byte result = 0;
+ if (zuluFlag) result = Timezone.getZuluCode();
+ else result = new Timezone(tzHours, tzMinutes).getCode();
+ if (localFlag) result |= LOCAL_BIT;
+ if (midnight) result |= MIDNIGHT_BIT;
+ return result;
+ }
+
+ /**
+ * Decodes a {@link ByteBuffer} into a LexicalDateTime.
+ * @param bb The ByteBuffer to decode.
+ * @return a new LexicalDateTime structure.
+ */
+ public static LexicalDateTime decode(ByteBuffer bb) {
+ assert bb.limit() >= PLACES_OFFSET;
+ return decode(bb.getLong(0), bb.get(TZ_OFFSET), bb.get(PLACES_OFFSET));
+ }
+
+ /**
+ * Decodes a millisecond value and an encoded byte into a timezone and flags.
+ * @param millis The milliseconds since the epoch.
+ * @param timezoneState The encoded data representing the timezone.
+ * @param places The number of decimal places for the seconds representation.
+ * @return a new LexicalDateTime structure.
+ */
+ public static LexicalDateTime decode(long millis, byte timezoneState, byte places) {
+ boolean local = (timezoneState & LOCAL_BIT) != 0;
+ boolean midnight = (timezoneState & MIDNIGHT_BIT) != 0;
+ byte tzCode = (byte)(timezoneState & TZ_MASK);
+ boolean zulu = (tzCode == Timezone.getZuluCode());
+ Timezone tz = new Timezone(tzCode);
+ return new LexicalDateTime(millis, tz.getHour(), tz.getMinute(), midnight, places, local, zulu);
+ }
+
+ /** Return a lexical representation of this dateTime. */
+ public String toString() {
+ if (cachedDateTime == null) {
+ DateTimeZone dtz;
+ dtz = (localFlag) ? null : DateTimeZone.forOffsetHoursMinutes(tzHours, tzMinutes);
+ cachedDateTime = new DateTime(millis, dtz);
+ }
+ StringBuilder result;
+ if (!midnight) {
+ result = new StringBuilder(MAIN_FORMATTER.print(cachedDateTime));
+ if (milliPlaces > 0) {
+ result.append(MILLI_SEPARATOR_STR);
+ int place = MILLIS;
+ long fraction = millis;
+ if (fraction < 0) fraction = fraction % place + place;
+ for (int m = 0; m < milliPlaces; m++) {
+ fraction = fraction % place;
+ place /= 10;
+ result.append(fraction / place);
+ }
+ }
+ } else {
+ result = new StringBuilder(DATE_FORMATTER.print(cachedDateTime.plusDays(-1)));
+ result.append(MIDNIGHT_STR);
+ if (milliPlaces > 0) {
+ result.append(MILLI_SEPARATOR_STR);
+ for (int i = 0; i < milliPlaces; i++) result.append("0");
+ }
+ }
+ if (!localFlag) {
+ if (zuluFlag) result.append(ZULU_STR);
+ else result.append(String.format("%+03d:%02d", tzHours, tzMinutes));
+ }
+ return result.toString();
+ }
+
+ /**
+ * Parse a dateTime string. It <strong>must</strong> be of the form:
+ * ('-')? yyyy '-' MM '-' dd 'T' hh ':' mm ':' ss ( '.' s+ )? ( ( ('+'|'-')? hh ':' mm ) | 'Z' )?
+ * @param dt The dateTime string to parse.
+ * @return a new LexcalDateTime value.
+ * @throws ParseException If a character that doesn't match the above pattern is discovered.
+ */
+ public static LexicalDateTime parseDateTime(String dt) throws ParseException {
+ int pos = 0;
+ boolean negative = dt.charAt(pos) == '-';
+ if (negative) pos++;
+ int year = d(dt, pos++) * 1000 + d(dt, pos++) * 100 + d(dt, pos++) * 10 + d(dt, pos++);
+ while (dt.charAt(pos) != DATE_SEPARATOR) year = year * 10 + d(dt, pos++);
+ if (negative) year = -year;
+ if (dt.charAt(pos++) != DATE_SEPARATOR) throw new ParseException(BAD_FORMAT + "date: " + dt, pos - 1);
+ int month = d(dt, pos++) * 10 + d(dt, pos++);
+ if (dt.charAt(pos++) != DATE_SEPARATOR) throw new ParseException(BAD_FORMAT + "date: " + dt, pos - 1);
+ int day = d(dt, pos++) * 10 + d(dt, pos++);
+
+ if (dt.charAt(pos++) != DATE_TIME_SEPARATOR) throw new ParseException(BAD_FORMAT + "date/time: " + dt, pos - 1);
+
+ int hour = d(dt, pos++) * 10 + d(dt, pos++);
+ if (dt.charAt(pos++) != TIME_SEPARATOR) throw new ParseException(BAD_FORMAT + "time: " + dt, pos - 1);
+ int minute = d(dt, pos++) * 10 + d(dt, pos++);
+ if (dt.charAt(pos++) != TIME_SEPARATOR) throw new ParseException(BAD_FORMAT + "time: " + dt, pos - 1);
+ int second = d(dt, pos++) * 10 + d(dt, pos++);
+
+ int millisecs = 0;
+ byte milliPlaces = 0;
+ int lastPos = dt.length() - 1;
+ if (pos < lastPos) {
+ if (dt.charAt(pos) == MILLI_SEPARATOR) {
+ int place = MILLIS / 10;
+ int digit;
+ while (isDecimal((digit = dt.charAt(++pos) - '0'))) {
+ millisecs += digit * place;
+ if (milliPlaces++ > 3) throw new ParseException(BAD_FORMAT + "milliseconds: " + dt, pos);
+ place /= 10;
+ if (pos == lastPos) break;
+ }
+ }
+ }
+
+ boolean midnightFlag = false;
+ if (hour == MIDNIGHT) {
+ midnightFlag = true;
+ hour = 0;
+ }
+ if (midnightFlag && (minute > 0 || second > 0 || millisecs > 0)) throw new ParseException(BAD_FORMAT + "time: " + dt, pos);
+
+ boolean local = false;
+ int tzHour = 0;
+ int tzMinute = 0;
+ boolean zuluFlag = false;
+ DateTimeZone timezone = null;
+ if (pos <= lastPos) {
+ char tz = dt.charAt(pos++);
+ if (tz == ZULU) {
+ if (pos != lastPos + 1) throw new ParseException(BAD_FORMAT + "timezone: " + dt, pos);
+ timezone = UTC;
+ zuluFlag = true;
+ } else {
+ if (pos != lastPos - 4 || (tz != NEG_TZ && tz != POS_TZ)) throw new ParseException(BAD_FORMAT + "timezone: " + dt, pos);
+ tzHour = d(dt, pos++) * 10 + d(dt, pos++);
+ if (dt.charAt(pos++) != TIME_SEPARATOR) throw new ParseException(BAD_FORMAT + "timezone: " + dt, pos - 1);
+ tzMinute = d(dt, pos++) * 10 + d(dt, pos++);
+ if (tz == NEG_TZ) tzHour = -tzHour;
+ timezone = DateTimeZone.forOffsetHoursMinutes(tzHour, tzMinute);
+ }
+ } else {
+ local = true;
+ }
+
+ DateTime dateTime = new DateTime(year, month, day, hour, minute, second, millisecs, timezone);
+ if (midnightFlag) dateTime = dateTime.plusDays(1);
+ return new LexicalDateTime(dateTime, tzHour, tzMinute, midnightFlag, milliPlaces, local, zuluFlag);
+ }
+
+ /**
+ * Check that the timezone flags are consistent with one another.
+ * @throws IllegalArgumentException if the is an inconsistency in the timezone values.
+ */
+ private void testTimezoneConsistency() {
+ if (zuluFlag) {
+ if (localFlag) throw new IllegalArgumentException("Cannot have Zulu time and a \"default\" timezone");
+ if (tzHours != 0 || tzMinutes != 0) throw new IllegalArgumentException("Cannot have Zulu time and a timezone offset");
+ }
+ assert (millis % (int)Math.pow(10, 3 - milliPlaces)) == 0;
+ }
+
+ private static int d(String str, int i) throws ParseException {
+ int d = str.charAt(i) - '0';
+ if (d >= 10 || d < 0) throw new ParseException("Unexpected character: " + Character.toString(str.charAt(i)) + ". Expected numeric digit.", i);
+ return d;
+ }
+
+ private static boolean isDecimal(int i) {
+ return i < 10 && i >= 0;
+ }
+
+}
Added: trunk/src/jar/util/java/org/mulgara/util/LexicalDateTimeUnitTest.java
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/LexicalDateTimeUnitTest.java (rev 0)
+++ trunk/src/jar/util/java/org/mulgara/util/LexicalDateTimeUnitTest.java 2008-06-06 04:03:07 UTC (rev 974)
@@ -0,0 +1,162 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.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.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.util;
+
+import java.nio.ByteBuffer;
+import java.text.ParseException;
+
+import junit.framework.Test;
+import junit.framework.TestCase;
+import junit.framework.TestSuite;
+
+/**
+ * Test cases for LexicalDateTime
+ *
+ * @created Jun 5, 2008
+ * @author Paul Gearon
+ * @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class LexicalDateTimeUnitTest extends TestCase {
+
+ String[] dtStrings = new String[] {
+ "2002-10-10T12:00:12.34-05:00",
+ "2002-10-10T12:00:12.34+05:00",
+ "2002-10-10T12:00:12.34+00:00",
+ "2002-10-10T00:00:12.34+00:00",
+ "2002-10-10T24:00:00.00+00:00",
+ "2002-10-10T12:00:12.34+10:00",
+ "2002-10-10T12:00:12.34Z",
+ "2002-10-10T00:00:12.34Z",
+ "2002-10-10T24:00:00.00Z",
+ "2002-10-10T12:00:12.345-05:00",
+ "2002-10-10T12:00:12.345+05:00",
+ "2002-10-10T12:00:12.345+00:00",
+ "2002-10-10T00:00:12.345+00:00",
+ "2002-10-10T24:00:00.000+00:00",
+ "2002-10-10T12:00:12.345+10:00",
+ "2002-10-10T12:00:12.345Z",
+ "2002-10-10T00:00:12.345Z",
+ "2002-10-10T24:00:00.000Z"
+ };
+
+ String[] negStrings = new String[] {
+ "-0002-10-10T12:00:12.34-05:00",
+ "-0002-10-10T12:00:12.34+05:00",
+ "-0002-10-10T12:00:12.34+00:00",
+ "-0002-10-10T00:00:12.34+00:00",
+ "-0002-10-10T24:00:00.00+00:00",
+ "-0002-10-10T12:00:12.34+10:00",
+ "-0002-10-10T12:00:12.34Z",
+ "-0002-10-10T00:00:12.34Z",
+ "-0002-10-10T24:00:00.00Z",
+ "-0002-10-10T12:00:12.345-05:00",
+ "-0002-10-10T12:00:12.345+05:00",
+ "-0002-10-10T12:00:12.345+00:00",
+ "-0002-10-10T00:00:12.345+00:00",
+ "-0002-10-10T24:00:00.000+00:00",
+ "-0002-10-10T12:00:12.345+10:00",
+ "-0002-10-10T12:00:12.345Z",
+ "-0002-10-10T00:00:12.345Z",
+ "-0002-10-10T24:00:00.000Z"
+ };
+
+ String[] largeStrings = new String[] {
+ "12002-10-10T12:00:12.34-05:00",
+ "120002-10-10T12:00:12.34-05:00",
+ "-12002-10-10T12:00:12.34-05:00",
+ "-120002-10-10T12:00:12.34-05:00"
+ };
+
+ public LexicalDateTimeUnitTest(String name) {
+ super(name);
+ }
+
+ /**
+ * Hook for test runner to obtain a test suite from.
+ * @return The test suite to run.
+ */
+ public static Test suite() {
+ TestSuite suite = new TestSuite();
+ suite.addTest(new LexicalDateTimeUnitTest("testParseDateTime"));
+ suite.addTest(new LexicalDateTimeUnitTest("testEncode"));
+ suite.addTest(new LexicalDateTimeUnitTest("testEncodeTimezoneState"));
+ return suite;
+ }
+
+ /**
+ * Default test runner.
+ *
+ * @param args The command line arguments
+ */
+ public static void main(String[] args) {
+ junit.textui.TestRunner.run(suite());
+ }
+
+ /**
+ * Test method for {@link org.mulgara.util.LexicalDateTime#parseDateTime(java.lang.String)}.
+ */
+ public void testParseDateTime() throws ParseException {
+ parseDateTimeHelper(dtStrings);
+ parseDateTimeHelper(negStrings);
+ parseDateTimeHelper(largeStrings);
+ }
+
+ void parseDateTimeHelper(String[] strings) throws ParseException {
+ for (String l: strings) {
+ LexicalDateTime dt = LexicalDateTime.parseDateTime(l);
+ assertEquals(l, dt.toString());
+ }
+ }
+
+ /**
+ * Test method for {@link org.mulgara.util.LexicalDateTime#encode(java.nio.ByteBuffer)}.
+ */
+ public void testEncode() throws ParseException {
+ encodeHelper(dtStrings);
+ encodeHelper(negStrings);
+ encodeHelper(largeStrings);
+ }
+
+ void encodeHelper(String[] strings) throws ParseException {
+ for (String l: strings) {
+ LexicalDateTime dt = LexicalDateTime.parseDateTime(l);
+
+ ByteBuffer bb = ByteBuffer.allocate(16);
+ LexicalDateTime newDt = LexicalDateTime.decode(dt.encode(bb));
+
+ assertEquals(l, newDt.toString());
+ }
+ }
+
+ /**
+ * Test method for {@link org.mulgara.util.LexicalDateTime#encodeTimezoneState()}.
+ */
+ public void testEncodeTimezoneState() throws ParseException {
+ encodeTimezoneStateHelper(dtStrings);
+ encodeTimezoneStateHelper(negStrings);
+ encodeTimezoneStateHelper(largeStrings);
+ }
+
+ void encodeTimezoneStateHelper(String[] strings) throws ParseException {
+ for (String l: strings) {
+ LexicalDateTime dt = LexicalDateTime.parseDateTime(l);
+ long millis = dt.getMillis();
+ byte tzstate = dt.encodeTimezoneState();
+ byte dec = dt.getDecimalPlaces();
+
+ LexicalDateTime newDt = LexicalDateTime.decode(millis, tzstate, dec);
+ assertEquals(l, newDt.toString());
+ }
+ }
+}
Added: trunk/src/jar/util/java/org/mulgara/util/Timezone.java
===================================================================
--- trunk/src/jar/util/java/org/mulgara/util/Timezone.java (rev 0)
+++ trunk/src/jar/util/java/org/mulgara/util/Timezone.java 2008-06-06 04:03:07 UTC (rev 974)
@@ -0,0 +1,145 @@
+/*
+ * The contents of this file are subject to the Open Software License
+ * Version 3.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.opensource.org/licenses/osl-3.0.txt
+ *
+ * Software distributed under the License is distributed on an "AS IS"
+ * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ * the License for the specific language governing rights and limitations
+ * under the License.
+ */
+
+package org.mulgara.util;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Maps from a known hour:minute offset for a timezone into a 6 bit code, and back.
+ *
+ * @created Jun 5, 2008
+ * @author Paul Gearon
+ * @copyright © 2008 <a href="http://www.topazproject.org/">The Topaz Project</a>
+ * @licence <a href="{@docRoot}/../../LICENCE.txt">Open Software License v3.0</a>
+ */
+public class Timezone {
+
+ /** A mask for restricting bit patterns to a byte (removing sign extension) */
+ private static final int BYTE_MASK = 0xFF;
+
+ /** The construct that holds the offset details */
+ private HourMinute hm;
+
+ /** The internal code for the timezone offset. */
+ private byte internalCode;
+
+ /**
+ * Constructs a Timezone using a code.
+ * @param code The code for a known timezone.
+ * @throws IllegalArgumentException The code does not correspond to a timezone in the database.
+ */
+ public Timezone(byte code) {
+ if ((code & 3) != 0) throw new IllegalArgumentException("Invalid bit pattern in the timezone code");
+ internalCode = (byte)((code & BYTE_MASK) >> 2);
+ if (internalCode > timezoneList.length || internalCode < 0) throw new IllegalArgumentException("Unknown timezone code: " + code);
+ hm = (code == ZULU_CODE) ? ZULU : timezoneList[internalCode];
+ }
+
+ /**
+ * Constructs a Timezone using an hour:minute offset.
+ * @param hour The hour offset of the timezone. This cannot encode ZULU time.
+ * @param minute The minute offset of the timezone.
+ * @throws IllegalArgumentException The timezone is not in the database of known timezones.
+ */
+ public Timezone(int hour, int minute) {
+ hm = new HourMinute(hour, minute);
+ Byte c = tzCodes.get(hm);
+ if (c == null) throw new IllegalArgumentException("Timezone is not in official database: " + hm);
+ internalCode = c;
+ }
+
+ /** Gets the hour for this timezone. */
+ public int getHour() {
+ return hm.hour;
+ }
+
+ /** Gets the minute for this timezone. */
+ public int getMinute() {
+ return hm.minute;
+ }
+
+ /** Gets the code for this timezone. This is guaranteed to use the top 5 bits, and set the bottom 2 to 0.*/
+ public byte getCode() {
+ return (byte)(internalCode << 2);
+ }
+
+ /** Gets the code for the ZULU timezone. */
+ public static byte getZuluCode() {
+ return ZULU_CODE;
+ }
+
+ /** The ZULU timezone */
+ private static final HourMinute ZULU = new HourMinute(0, 0);
+
+ /** The database of known timezones. @see http://en.wikipedia.org/wiki/List_of_zoneinfo_timezones */
+ private static final HourMinute[] timezoneList = new HourMinute[] {
+ new HourMinute(-12, 0), new HourMinute(-11, 0), new HourMinute(-10, 0), new HourMinute(-9, 30),
+ new HourMinute(-9, 0), new HourMinute(-8, 0), new HourMinute(-7, 0), new HourMinute(-6, 0),
+ new HourMinute(-5, 0), new HourMinute(-4, 30), new HourMinute(-4, 0), new HourMinute(-3, 30),
+ new HourMinute(-3, 0), new HourMinute(-2, 0), new HourMinute(-1, 0), ZULU,
+ new HourMinute(1, 0), new HourMinute(2, 0), new HourMinute(3, 0), new HourMinute(3, 30),
+ new HourMinute(4, 0), new HourMinute(4, 30), new HourMinute(5, 0), new HourMinute(5, 30),
+ new HourMinute(5, 45), new HourMinute(6, 0), new HourMinute(6, 30), new HourMinute(7, 0),
+ new HourMinute(8, 0), new HourMinute(8, 45), new HourMinute(9, 0), new HourMinute(9, 30),
+ new HourMinute(10, 0), new HourMinute(10, 30), new HourMinute(11, 0), new HourMinute(11, 30),
+ new HourMinute(12, 0), new HourMinute(12, 45), new HourMinute(13, 0), new HourMinute(14, 0)
+ };
+
+ /** A map of timezones to their code. */
+ private static final Map<HourMinute,Byte> tzCodes;
+
+ /** A special code for ZULU, to distinguish it from 00:00. This is an external code, so the lowest 2 bits must be 0. */
+ public static final byte ZULU_CODE = (byte)(timezoneList.length << 2);
+
+ // populates the tzCodes with the timezoneList
+ static {
+ Map<HourMinute,Byte> writeMap = new HashMap<HourMinute,Byte>();
+ for (byte t = 0; t < timezoneList.length; t++) writeMap.put(timezoneList[t], t);
+ tzCodes = Collections.unmodifiableMap(writeMap);
+ }
+
+ /**
+ * A private structure for associating an hour and minute together.
+ */
+ private static class HourMinute {
+ /** The hour value. */
+ public final int hour;
+ /** The minute value. */
+ public final int minute;
+
+ /** Constructs an hour and minute tuple */
+ public HourMinute(int h, int m) {
+ hour = h;
+ minute = m;
+ }
+
+ /** @inheritDoc */
+ public int hashCode() {
+ return (hour * 4) + (minute / 15);
+ }
+
+ /** @inheritDoc */
+ public boolean equals(Object o) {
+ return (o instanceof HourMinute) && ((HourMinute)o).hour == hour && ((HourMinute)o).minute == minute;
+ }
+
+ /** @inheritDoc */
+ public String toString() {
+ if (hour == 0 && minute == 0) return "00:00";
+ return String.format("%02d:%02d", hour, minute);
+ }
+ }
+
+}
More information about the Mulgara-svn
mailing list