0001: /*
0002: * Geotools2 - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2002, Geotools Project Managment Committee (PMC)
0005: *
0006: * This library is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU Lesser General Public
0008: * License as published by the Free Software Foundation;
0009: * version 2.1 of the License.
0010: *
0011: * This library is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0014: * Lesser General Public License for more details.
0015: *
0016: */
0017: /*
0018: Copyright (c) 2002-2004, Dennis M. Sosnoski.
0019: All rights reserved.
0020: Redistribution and use in source and binary forms, with or without modification,
0021: are permitted provided that the following conditions are met:
0022: * Redistributions of source code must retain the above copyright notice, this
0023: list of conditions and the following disclaimer.
0024: * Redistributions in binary form must reproduce the above copyright notice,
0025: this list of conditions and the following disclaimer in the documentation
0026: and/or other materials provided with the distribution.
0027: * Neither the name of JiBX nor the names of its contributors may be used
0028: to endorse or promote products derived from this software without specific
0029: prior written permission.
0030: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
0031: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0032: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0033: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
0034: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0035: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0036: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
0037: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0038: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0039: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0040: */
0041: package org.geotools.feature.iso.attribute;
0042:
0043: import java.sql.Time;
0044: import java.sql.Timestamp;
0045: import java.util.ArrayList;
0046: import java.util.Date;
0047: import java.util.List;
0048: import java.util.TimeZone;
0049:
0050: /**
0051: * Utility class supplying static methods. Date serialization is based on the
0052: * algorithms published by Peter Baum (http://www.capecod.net/~pbaum). All
0053: * date handling is done according to the W3C Schema specification, which uses
0054: * a proleptic Gregorian calendar with no year 0. Note that this differs from
0055: * the Java date handling, which uses a discontinuous Gregorian calendar.
0056: *
0057: * @author Dennis M. Sosnoski
0058: * @version 1.0
0059: */
0060: public abstract class DateUtil {
0061: /** Number of milliseconds in a minute. */
0062: private static final int MSPERMINUTE = 60000;
0063:
0064: /** Number of milliseconds in an hour. */
0065: private static final int MSPERHOUR = MSPERMINUTE * 60;
0066:
0067: /** Number of milliseconds in a day. */
0068: private static final int MSPERDAY = MSPERHOUR * 24;
0069:
0070: /** Number of milliseconds in a day as a long. */
0071: private static final long LMSPERDAY = (long) MSPERDAY;
0072:
0073: /** Number of milliseconds in a (non-leap) year. */
0074: private static final long MSPERYEAR = LMSPERDAY * 365;
0075:
0076: /** Average number of milliseconds in a year within century. */
0077: private static final long MSPERAVGYEAR = (long) (MSPERDAY * 365.25);
0078:
0079: /** Number of milliseconds in a normal century. */
0080: private static final long MSPERCENTURY = (long) (MSPERDAY * 36524.25);
0081:
0082: /**
0083: * Millisecond value of base time for internal representation. This gives
0084: * the bias relative to January 1 of the year 1 C.E.
0085: */
0086: private static final long TIME_BASE = (1969 * MSPERYEAR)
0087: + (((1969 / 4) - 19 + 4) * LMSPERDAY);
0088:
0089: /** Day number for start of month in non-leap year. */
0090: private static final int[] MONTHS_NONLEAP = { 0, 31, 59, 90, 120,
0091: 151, 181, 212, 243, 273, 304, 334, 365 };
0092:
0093: /** Day number for start of month in non-leap year. */
0094: private static final int[] MONTHS_LEAP = { 0, 31, 60, 91, 121, 152,
0095: 182, 213, 244, 274, 305, 335, 366 };
0096:
0097: /** Millisecond count prior to start of month in March 1-biased year. */
0098: private static final long[] BIAS_MONTHMS = { 0 * LMSPERDAY,
0099: 0 * LMSPERDAY, 0 * LMSPERDAY, 0 * LMSPERDAY,
0100: 31 * LMSPERDAY, 61 * LMSPERDAY, 92 * LMSPERDAY,
0101: 122 * LMSPERDAY, 153 * LMSPERDAY, 184 * LMSPERDAY,
0102: 214 * LMSPERDAY, 245 * LMSPERDAY, 275 * LMSPERDAY,
0103: 306 * LMSPERDAY, 337 * LMSPERDAY };
0104:
0105: /** Pad character for base64 encoding. */
0106: private static final char PAD_CHAR = '=';
0107:
0108: /** Characters used in base64 encoding. */
0109: private static final char[] s_base64Chars = { 'A', 'B', 'C', 'D',
0110: 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
0111: 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b',
0112: 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
0113: 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
0114: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' };
0115:
0116: /** Values corresponding to characters used in bas64 encoding. */
0117: private static final byte[] s_base64Values = new byte[128];
0118:
0119: static {
0120: for (int i = 0; i < s_base64Values.length; i++) {
0121: s_base64Values[i] = -1;
0122: }
0123:
0124: s_base64Values[PAD_CHAR] = 0;
0125:
0126: for (int i = 0; i < s_base64Chars.length; i++) {
0127: s_base64Values[s_base64Chars[i]] = (byte) i;
0128: }
0129: }
0130:
0131: /**
0132: * Parse digits in text as integer value. This internal method is used for
0133: * number values embedded within lexical structures. Only decimal digits
0134: * can be included in the text range parsed.
0135: *
0136: * @param text text to be parsed
0137: * @param offset starting offset in text
0138: * @param length number of digits to be parsed
0139: *
0140: * @return converted positive integer value
0141: *
0142: * @throws IllegalArgumentException on parse error
0143: */
0144: private static int parseDigits(String text, int offset, int length)
0145: throws IllegalArgumentException {
0146: // check if overflow a potential problem
0147: int value = 0;
0148:
0149: if (length > 9) {
0150: // use library parse code for potential overflow
0151: try {
0152: value = Integer.parseInt(text.substring(offset, offset
0153: + length));
0154: } catch (NumberFormatException ex) {
0155: throw new IllegalArgumentException(ex.getMessage());
0156: }
0157: } else {
0158: // parse with no overflow worries
0159: int limit = offset + length;
0160:
0161: while (offset < limit) {
0162: char chr = text.charAt(offset++);
0163:
0164: if ((chr >= '0') && (chr <= '9')) {
0165: value = (value * 10) + (chr - '0');
0166: } else {
0167: throw new IllegalArgumentException(
0168: "Non-digit in number value");
0169: }
0170: }
0171: }
0172:
0173: return value;
0174: }
0175:
0176: /**
0177: * Parse integer value from text. Integer values are parsed with optional
0178: * leading sign flag, followed by any number of digits.
0179: *
0180: * @param text text to be parsed
0181: *
0182: * @return converted integer value
0183: *
0184: * @throws IllegalArgumentException on parse error
0185: */
0186: public static int parseInt(String text)
0187: throws IllegalArgumentException {
0188: // make sure there's text to be processed
0189: text = text.trim();
0190:
0191: int offset = 0;
0192: int limit = text.length();
0193:
0194: if (limit == 0) {
0195: throw new IllegalArgumentException("Empty number value");
0196: }
0197:
0198: // check leading sign present in text
0199: boolean negate = false;
0200: char chr = text.charAt(0);
0201:
0202: if (chr == '-') {
0203: if (limit > 9) {
0204: // special case to make sure maximum negative value handled
0205: try {
0206: return Integer.parseInt(text);
0207: } catch (NumberFormatException ex) {
0208: throw new IllegalArgumentException(ex.getMessage());
0209: }
0210: } else {
0211: negate = true;
0212: offset++;
0213: }
0214: } else if (chr == '+') {
0215: offset++;
0216: }
0217:
0218: if (offset >= limit) {
0219: throw new IllegalArgumentException("Invalid number format");
0220: }
0221:
0222: // handle actual value conversion
0223: int value = parseDigits(text, offset, limit - offset);
0224:
0225: if (negate) {
0226: return -value;
0227: } else {
0228: return value;
0229: }
0230: }
0231:
0232: /**
0233: * Serialize int value to text.
0234: *
0235: * @param value int value to be serialized
0236: *
0237: * @return text representation of value
0238: */
0239: public static String serializeInt(int value) {
0240: return Integer.toString(value);
0241: }
0242:
0243: /**
0244: * Parse long value from text. Long values are parsed with optional leading
0245: * sign flag, followed by any number of digits.
0246: *
0247: * @param text text to be parsed
0248: *
0249: * @return converted long value
0250: *
0251: * @throws IllegalArgumentException on parse error
0252: */
0253: public static long parseLong(String text)
0254: throws IllegalArgumentException {
0255: // make sure there's text to be processed
0256: text = text.trim();
0257:
0258: int offset = 0;
0259: int limit = text.length();
0260:
0261: if (limit == 0) {
0262: throw new IllegalArgumentException("Empty number value");
0263: }
0264:
0265: // check leading sign present in text
0266: boolean negate = false;
0267: char chr = text.charAt(0);
0268:
0269: if (chr == '-') {
0270: negate = true;
0271: offset++;
0272: } else if (chr == '+') {
0273: offset++;
0274: }
0275:
0276: if (offset >= limit) {
0277: throw new IllegalArgumentException("Invalid number format");
0278: }
0279:
0280: // check if overflow a potential problem
0281: long value = 0;
0282:
0283: if ((limit - offset) > 18) {
0284: // pass text to library parse code (less leading +)
0285: if (chr == '+') {
0286: text = text.substring(1);
0287: }
0288:
0289: try {
0290: value = Long.parseLong(text);
0291: } catch (NumberFormatException ex) {
0292: throw new IllegalArgumentException(ex.getMessage());
0293: }
0294: } else {
0295: // parse with no overflow worries
0296: while (offset < limit) {
0297: chr = text.charAt(offset++);
0298:
0299: if ((chr >= '0') && (chr <= '9')) {
0300: value = (value * 10) + (chr - '0');
0301: } else {
0302: throw new IllegalArgumentException(
0303: "Non-digit in number value");
0304: }
0305: }
0306:
0307: if (negate) {
0308: value = -value;
0309: }
0310: }
0311:
0312: return value;
0313: }
0314:
0315: /**
0316: * Serialize long value to text.
0317: *
0318: * @param value long value to be serialized
0319: *
0320: * @return text representation of value
0321: */
0322: public static String serializeLong(long value) {
0323: return Long.toString(value);
0324: }
0325:
0326: /**
0327: * Parse short value from text. Short values are parsed with optional
0328: * leading sign flag, followed by any number of digits.
0329: *
0330: * @param text text to be parsed
0331: *
0332: * @return converted short value
0333: *
0334: * @throws IllegalArgumentException on parse error
0335: */
0336: public static short parseShort(String text)
0337: throws IllegalArgumentException {
0338: int value = parseInt(text);
0339:
0340: if ((value < Short.MIN_VALUE) || (value > Short.MAX_VALUE)) {
0341: throw new IllegalArgumentException("Value out of range");
0342: }
0343:
0344: return (short) value;
0345: }
0346:
0347: /**
0348: * Serialize short value to text.
0349: *
0350: * @param value short value to be serialized
0351: *
0352: * @return text representation of value
0353: */
0354: public static String serializeShort(short value) {
0355: return Short.toString(value);
0356: }
0357:
0358: /**
0359: * Parse byte value from text. Byte values are parsed with optional leading
0360: * sign flag, followed by any number of digits.
0361: *
0362: * @param text text to be parsed
0363: *
0364: * @return converted byte value
0365: *
0366: * @throws IllegalArgumentException on parse error
0367: */
0368: public static byte parseByte(String text)
0369: throws IllegalArgumentException {
0370: int value = parseInt(text);
0371:
0372: if ((value < Byte.MIN_VALUE) || (value > Byte.MAX_VALUE)) {
0373: throw new IllegalArgumentException("Value out of range");
0374: }
0375:
0376: return (byte) value;
0377: }
0378:
0379: /**
0380: * Serialize byte value to text.
0381: *
0382: * @param value byte value to be serialized
0383: *
0384: * @return text representation of value
0385: */
0386: public static String serializeByte(byte value) {
0387: return Byte.toString(value);
0388: }
0389:
0390: /**
0391: * Parse boolean value from text. Boolean values are parsed as either text
0392: * "true" and "false", or "1" and "0" numeric equivalents.
0393: *
0394: * @param text text to be parsed
0395: *
0396: * @return converted boolean value
0397: *
0398: * @throws IllegalArgumentException on parse error
0399: */
0400: public static boolean parseBoolean(String text)
0401: throws IllegalArgumentException {
0402: text = text.trim();
0403:
0404: if ("true".equals(text) || "1".equals(text)) {
0405: return true;
0406: } else if ("false".equals(text) || "0".equals(text)) {
0407: return false;
0408: } else {
0409: throw new IllegalArgumentException("Invalid boolean value");
0410: }
0411: }
0412:
0413: /**
0414: * Serialize boolean value to text. This serializes the value using the
0415: * text representation as "true" or "false".
0416: *
0417: * @param value boolean value to be serialized
0418: *
0419: * @return text representation of value
0420: */
0421: public static String serializeBoolean(boolean value) {
0422: return value ? "true" : "false";
0423: }
0424:
0425: /**
0426: * Parse char value from text as unsigned 16-bit integer. Char values are
0427: * parsed with optional leading sign flag, followed by any number of
0428: * digits.
0429: *
0430: * @param text text to be parsed
0431: *
0432: * @return converted char value
0433: *
0434: * @throws IllegalArgumentException on parse error
0435: */
0436: public static char parseChar(String text)
0437: throws IllegalArgumentException {
0438: int value = parseInt(text);
0439:
0440: if ((value < Character.MIN_VALUE)
0441: || (value > Character.MAX_VALUE)) {
0442: throw new IllegalArgumentException("Value out of range");
0443: }
0444:
0445: return (char) value;
0446: }
0447:
0448: /**
0449: * Serialize char value to text as unsigned 16-bit integer.
0450: *
0451: * @param value char value to be serialized
0452: *
0453: * @return text representation of value
0454: */
0455: public static String serializeChar(char value) {
0456: return Integer.toString(value);
0457: }
0458:
0459: /**
0460: * Parse char value from text as character value. This requires that the
0461: * string must be of length one.
0462: *
0463: * @param text text to be parsed
0464: *
0465: * @return converted char value
0466: *
0467: * @throws IllegalArgumentException on parse error
0468: */
0469: public static char parseCharString(String text)
0470: throws IllegalArgumentException {
0471: if (text.length() == 1) {
0472: return text.charAt(0);
0473: } else {
0474: throw new IllegalArgumentException(
0475: "Input must be a single character");
0476: }
0477: }
0478:
0479: /**
0480: * Deserialize char value from text as character value. This requires that
0481: * the string must be null or of length one.
0482: *
0483: * @param text text to be parsed (may be <code>null</code>)
0484: *
0485: * @return converted char value
0486: *
0487: * @throws IllegalArgumentException on parse error
0488: */
0489: public static char deserializeCharString(String text)
0490: throws IllegalArgumentException {
0491: if (text == null) {
0492: return 0;
0493: } else {
0494: return parseCharString(text);
0495: }
0496: }
0497:
0498: /**
0499: * Serialize char value to text as string of length one.
0500: *
0501: * @param value char value to be serialized
0502: *
0503: * @return text representation of value
0504: */
0505: public static String serializeCharString(char value) {
0506: return String.valueOf(value);
0507: }
0508:
0509: /**
0510: * Parse float value from text. This uses the W3C XML Schema format for
0511: * floats, with the exception that it will accept "+NaN" and "-NaN" as
0512: * valid formats. This is not in strict compliance with the specification,
0513: * but is included for interoperability with other Java XML processing.
0514: *
0515: * @param text text to be parsed
0516: *
0517: * @return converted float value
0518: *
0519: * @throws IllegalArgumentException on parse error
0520: */
0521: public static float parseFloat(String text)
0522: throws IllegalArgumentException {
0523: text = text.trim();
0524:
0525: if ("-INF".equals(text)) {
0526: return Float.NEGATIVE_INFINITY;
0527: } else if ("INF".equals(text)) {
0528: return Float.POSITIVE_INFINITY;
0529: } else {
0530: try {
0531: return Float.parseFloat(text);
0532: } catch (NumberFormatException ex) {
0533: throw new IllegalArgumentException(ex.getMessage());
0534: }
0535: }
0536: }
0537:
0538: /**
0539: * Serialize float value to text.
0540: *
0541: * @param value float value to be serialized
0542: *
0543: * @return text representation of value
0544: */
0545: public static String serializeFloat(float value) {
0546: if (Float.isInfinite(value)) {
0547: return (value < 0.0f) ? "-INF" : "INF";
0548: } else {
0549: return Float.toString(value);
0550: }
0551: }
0552:
0553: /**
0554: * Parse double value from text. This uses the W3C XML Schema format for
0555: * doubles, with the exception that it will accept "+NaN" and "-NaN" as
0556: * valid formats. This is not in strict compliance with the specification,
0557: * but is included for interoperability with other Java XML processing.
0558: *
0559: * @param text text to be parsed
0560: *
0561: * @return converted double value
0562: *
0563: * @throws IllegalArgumentException on parse error
0564: */
0565: public static double parseDouble(String text)
0566: throws IllegalArgumentException {
0567: text = text.trim();
0568:
0569: if ("-INF".equals(text)) {
0570: return Double.NEGATIVE_INFINITY;
0571: } else if ("INF".equals(text)) {
0572: return Double.POSITIVE_INFINITY;
0573: } else {
0574: try {
0575: return Double.parseDouble(text);
0576: } catch (NumberFormatException ex) {
0577: throw new IllegalArgumentException(ex.getMessage());
0578: }
0579: }
0580: }
0581:
0582: /**
0583: * Serialize double value to text.
0584: *
0585: * @param value double value to be serialized
0586: *
0587: * @return text representation of value
0588: */
0589: public static String serializeDouble(double value) {
0590: if (Double.isInfinite(value)) {
0591: return (value < 0.0f) ? "-INF" : "INF";
0592: } else {
0593: return Double.toString(value);
0594: }
0595: }
0596:
0597: /**
0598: * Convert gYear text to Java date. Date values are expected to be in W3C
0599: * XML Schema standard format as CCYY, with optional leading sign.
0600: *
0601: * @param text text to be parsed
0602: *
0603: * @return start of year date as millisecond value from 1 C.E.
0604: *
0605: * @throws IllegalArgumentException on parse error
0606: */
0607: public static long parseYear(String text)
0608: throws IllegalArgumentException {
0609: // start by validating the length
0610: text = text.trim();
0611:
0612: boolean valid = true;
0613: int minc = 4;
0614: char chr = text.charAt(0);
0615:
0616: if (chr == '-') {
0617: minc = 5;
0618: } else if (chr == '+') {
0619: valid = false;
0620: }
0621:
0622: if (text.length() < minc) {
0623: valid = false;
0624: }
0625:
0626: if (!valid) {
0627: throw new IllegalArgumentException("Invalid year format");
0628: }
0629:
0630: // handle year conversion
0631: int year = parseInt(text);
0632:
0633: if (year == 0) {
0634: throw new IllegalArgumentException(
0635: "Year value 0 is not allowed");
0636: }
0637:
0638: if (year > 0) {
0639: year--;
0640: }
0641:
0642: long day = ((((long) year) * 365) + (year / 4)) - (year / 100)
0643: + (year / 400);
0644:
0645: return (day * MSPERDAY) - TIME_BASE;
0646: }
0647:
0648: /**
0649: * Convert gYearMonth text to Java date. Date values are expected to be in
0650: * W3C XML Schema standard format as CCYY-MM, with optional leading sign.
0651: *
0652: * @param text text to be parsed
0653: *
0654: * @return start of month in year date as millisecond value
0655: *
0656: * @throws IllegalArgumentException on parse error
0657: */
0658: public static long parseYearMonth(String text)
0659: throws IllegalArgumentException {
0660: // start by validating the length and basic format
0661: text = text.trim();
0662:
0663: boolean valid = true;
0664: int minc = 7;
0665: char chr = text.charAt(0);
0666:
0667: if (chr == '-') {
0668: minc = 8;
0669: } else if (chr == '+') {
0670: valid = false;
0671: }
0672:
0673: int split = text.length() - 3;
0674:
0675: if (text.length() < minc) {
0676: valid = false;
0677: } else {
0678: if (text.charAt(split) != '-') {
0679: valid = false;
0680: }
0681: }
0682:
0683: if (!valid) {
0684: throw new IllegalArgumentException("Invalid date format");
0685: }
0686:
0687: // handle year and month conversion
0688: int year = parseInt(text.substring(0, split));
0689:
0690: if (year == 0) {
0691: throw new IllegalArgumentException(
0692: "Year value 0 is not allowed");
0693: }
0694:
0695: int month = parseDigits(text, split + 1, 2) - 1;
0696:
0697: if ((month < 0) || (month > 11)) {
0698: throw new IllegalArgumentException(
0699: "Month value out of range");
0700: }
0701:
0702: boolean leap = ((year % 4) == 0)
0703: && !(((year % 100) == 0) && ((year % 400) != 0));
0704:
0705: if (year > 0) {
0706: year--;
0707: }
0708:
0709: long day = ((((long) year) * 365) + (year / 4)) - (year / 100)
0710: + (year / 400)
0711: + (leap ? MONTHS_LEAP : MONTHS_NONLEAP)[month];
0712:
0713: return (day * MSPERDAY) - TIME_BASE;
0714: }
0715:
0716: /**
0717: * Convert date text to Java date. Date values are expected to be in W3C
0718: * XML Schema standard format as CCYY-MM-DD, with optional leading sign
0719: * and trailing time zone (though the time zone is ignored in this case).
0720: *
0721: * @param text text to be parsed
0722: *
0723: * @return start of day in month and year date as millisecond value
0724: *
0725: * @throws IllegalArgumentException on parse error
0726: */
0727: public static long parseDate(String text)
0728: throws IllegalArgumentException {
0729: // start by validating the length and basic format
0730: boolean valid = true;
0731: int minc = 10;
0732: char chr = text.charAt(0);
0733:
0734: if (chr == '-') {
0735: minc = 11;
0736: } else if (chr == '+') {
0737: valid = false;
0738: }
0739:
0740: int split = text.length() - 6;
0741:
0742: if (text.length() < minc) {
0743: valid = false;
0744: } else {
0745: if ((text.charAt(split) != '-')
0746: || (text.charAt(split + 3) != '-')) {
0747: valid = false;
0748: }
0749: }
0750:
0751: if (!valid) {
0752: throw new IllegalArgumentException("Invalid date format");
0753: }
0754:
0755: // handle year, month, and day conversion
0756: int year = parseInt(text.substring(0, split));
0757:
0758: if (year == 0) {
0759: throw new IllegalArgumentException(
0760: "Year value 0 is not allowed");
0761: }
0762:
0763: int month = parseDigits(text, split + 1, 2) - 1;
0764:
0765: if ((month < 0) || (month > 11)) {
0766: throw new IllegalArgumentException(
0767: "Month value out of range");
0768: }
0769:
0770: long day = parseDigits(text, split + 4, 2) - 1;
0771: boolean leap = ((year % 4) == 0)
0772: && !(((year % 100) == 0) && ((year % 400) != 0));
0773: int[] starts = leap ? MONTHS_LEAP : MONTHS_NONLEAP;
0774:
0775: if ((day < 0) || (day >= (starts[month + 1] - starts[month]))) {
0776: throw new IllegalArgumentException("Day value out of range");
0777: }
0778:
0779: if (year > 0) {
0780: year--;
0781: }
0782:
0783: day += (((((long) year) * 365) + (year / 4)) - (year / 100)
0784: + (year / 400) + starts[month]);
0785:
0786: return (day * MSPERDAY) - TIME_BASE;
0787: }
0788:
0789: /**
0790: * Deserialize date from text. Date values are expected to match W3C XML
0791: * Schema standard format as CCYY-MM-DD, with optional leading minus sign
0792: * if necessary. This method follows standard JiBX deserializer usage
0793: * requirements by accepting a <code>null</code> input.
0794: *
0795: * @param text text to be parsed (may be <code>null</code>)
0796: *
0797: * @return converted date, or <code>null</code> if passed <code>null</code>
0798: * input
0799: *
0800: * @throws IllegalArgumentException on parse error
0801: */
0802: public static Date deserializeDate(String text)
0803: throws IllegalArgumentException {
0804: if (text == null) {
0805: return null;
0806: } else {
0807: return new Date(parseDate(text));
0808: }
0809: }
0810:
0811: /**
0812: * Deserialize SQL date from text. Date values are expected to match W3C
0813: * XML Schema standard format as CCYY-MM-DD, with optional leading minus
0814: * sign if necessary. This method follows standard JiBX deserializer usage
0815: * requirements by accepting a <code>null</code> input.
0816: *
0817: * @param text text to be parsed (may be <code>null</code>)
0818: *
0819: * @return converted date, or <code>null</code> if passed <code>null</code>
0820: * input
0821: *
0822: * @throws IllegalArgumentException on parse error
0823: */
0824: public static java.sql.Date deserializeSqlDate(String text)
0825: throws IllegalArgumentException {
0826: if (text == null) {
0827: return null;
0828: } else {
0829: // convert value, assuming maximum daylight time change of 2 hours
0830: long time = parseDate(text);
0831: time += (TimeZone.getDefault().getRawOffset() + (MSPERHOUR * 2));
0832:
0833: return new java.sql.Date(time);
0834: }
0835: }
0836:
0837: /**
0838: * Parse general time value from text. Time values are expected to be in
0839: * W3C XML Schema standard format as hh:mm:ss.fff, with optional leading
0840: * sign and trailing time zone.
0841: *
0842: * @param text text to be parsed
0843: * @param start offset of first character of time value
0844: * @param length number of characters in time value
0845: *
0846: * @return converted time as millisecond value
0847: *
0848: * @throws IllegalArgumentException on parse error
0849: */
0850: public static long parseTime(String text, int start, int length)
0851: throws IllegalArgumentException {
0852: // validate time value following date
0853: long milli = 0;
0854: boolean valid = (length > (start + 7))
0855: && (text.charAt(start + 2) == ':')
0856: && (text.charAt(start + 5) == ':');
0857:
0858: if (valid) {
0859: int hour = parseDigits(text, start, 2);
0860: int minute = parseDigits(text, start + 3, 2);
0861: int second = parseDigits(text, start + 6, 2);
0862:
0863: if ((hour > 23) || (minute > 59) || (second > 60)) {
0864: valid = false;
0865: } else {
0866: // convert to base millisecond in day
0867: milli = ((((hour * 60) + minute) * 60) + second) * 1000;
0868: start += 8;
0869:
0870: if (length > start) {
0871: // adjust for time zone
0872: if (text.charAt(length - 1) == 'Z') {
0873: length--;
0874: } else {
0875: char chr = text.charAt(length - 6);
0876:
0877: if ((chr == '-') || (chr == '+')) {
0878: hour = parseDigits(text, length - 5, 2);
0879: minute = parseDigits(text, length - 2, 2);
0880:
0881: if ((hour > 23) || (minute > 59)) {
0882: valid = false;
0883: } else {
0884: int offset = ((hour * 60) + minute) * 60 * 1000;
0885:
0886: if (chr == '-') {
0887: milli += offset;
0888: } else {
0889: milli -= offset;
0890: }
0891: }
0892:
0893: length -= 6;
0894: }
0895: }
0896:
0897: // check for trailing fractional second
0898: if (text.charAt(start) == '.') {
0899: double fraction = Double.parseDouble(text
0900: .substring(start, length));
0901: milli += (fraction * 1000.0);
0902: } else if (length > start) {
0903: valid = false;
0904: }
0905: }
0906: }
0907: }
0908:
0909: // check for valid result
0910: if (valid) {
0911: return milli;
0912: } else {
0913: throw new IllegalArgumentException(
0914: "Invalid dateTime format");
0915: }
0916: }
0917:
0918: /**
0919: * Parse general dateTime value from text. Date values are expected to be
0920: * in W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss.fff, with
0921: * optional leading sign and trailing time zone.
0922: *
0923: * @param text text to be parsed
0924: *
0925: * @return converted date as millisecond value
0926: *
0927: * @throws IllegalArgumentException on parse error
0928: */
0929: public static long parseDateTime(String text)
0930: throws IllegalArgumentException {
0931: // split text to convert portions separately
0932: int split = text.indexOf('T');
0933:
0934: if (split < 0) {
0935: throw new IllegalArgumentException(
0936: "Missing 'T' separator in dateTime");
0937: }
0938:
0939: return parseDate(text.substring(0, split))
0940: + parseTime(text, split + 1, text.length());
0941: }
0942:
0943: /**
0944: * Deserialize date from general dateTime text. Date values are expected to
0945: * match W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with
0946: * optional leading minus sign and trailing seconds decimal, as necessary.
0947: * This method follows standard JiBX deserializer usage requirements by
0948: * accepting a <code>null</code> input.
0949: *
0950: * @param text text to be parsed (may be <code>null</code>)
0951: *
0952: * @return converted date, or <code>null</code> if passed <code>null</code>
0953: * input
0954: *
0955: * @throws IllegalArgumentException on parse error
0956: */
0957: public static Date deserializeDateTime(String text)
0958: throws IllegalArgumentException {
0959: if (text == null) {
0960: return null;
0961: } else {
0962: return new Date(parseDateTime(text));
0963: }
0964: }
0965:
0966: /**
0967: * Deserialize timestamp from general dateTime text. Timestamp values are
0968: * represented in the same way as regular dates, but allow more precision
0969: * in the fractional second value (down to nanoseconds). This method
0970: * follows standard JiBX deserializer usage requirements by accepting a
0971: * <code>null</code> input.
0972: *
0973: * @param text text to be parsed (may be <code>null</code>)
0974: *
0975: * @return converted timestamp, or <code>null</code> if passed
0976: * <code>null</code> input
0977: *
0978: * @throws IllegalArgumentException on parse error
0979: */
0980: public static Timestamp deserializeTimestamp(String text)
0981: throws IllegalArgumentException {
0982: if (text == null) {
0983: return null;
0984: } else {
0985: // check for fractional second value present
0986: int split = text.indexOf('.');
0987: int nano = 0;
0988:
0989: if (split > 0) {
0990: // make sure there aren't multiple decimal points
0991: if (text.indexOf('.', split) > 0) {
0992: throw new IllegalArgumentException(
0993: "Not a valid dateTime value");
0994: }
0995:
0996: // scan through all digits following decimal point
0997: int limit = text.length();
0998: int scan = split;
0999:
1000: while (++scan < limit) {
1001: char chr = text.charAt(scan);
1002:
1003: if ((chr < '0') || (chr > '9')) {
1004: break;
1005: }
1006: }
1007:
1008: // parse digits following decimal point
1009: int length = scan - split - 1;
1010:
1011: if (length > 9) {
1012: length = 9;
1013: }
1014:
1015: nano = parseDigits(text, split + 1, length);
1016:
1017: // convert to number of nanoseconds
1018: while (length < 9) {
1019: nano *= 10;
1020: length++;
1021: }
1022:
1023: // strip fractional second off text
1024: if (scan < limit) {
1025: text = text.substring(0, split)
1026: + text.substring(scan);
1027: } else {
1028: text = text.substring(0, split);
1029: }
1030: }
1031:
1032: // return timestamp value with nanoseconds
1033: Timestamp stamp = new Timestamp(parseDateTime(text));
1034: stamp.setNanos(nano);
1035:
1036: return stamp;
1037: }
1038: }
1039:
1040: /**
1041: * Deserialize time from text. Time values obey the rules of the time
1042: * portion of a dataTime value. This method follows standard JiBX
1043: * deserializer usage requirements by accepting a <code>null</code> input.
1044: *
1045: * @param text text to be parsed (may be <code>null</code>)
1046: *
1047: * @return converted time, or <code>null</code> if passed <code>null</code>
1048: * input
1049: *
1050: * @throws IllegalArgumentException on parse error
1051: */
1052: public static Time deserializeSqlTime(String text)
1053: throws IllegalArgumentException {
1054: if (text == null) {
1055: return null;
1056: } else {
1057: return new Time(parseTime(text, 0, text.length()));
1058: }
1059: }
1060:
1061: /**
1062: * Format year number consistent with W3C XML Schema definitions, using a
1063: * minimum of four digits padded with zeros if necessary. A leading minus
1064: * sign is included for years prior to 1 C.E.
1065: *
1066: * @param year number to be formatted
1067: * @param buff text formatting buffer
1068: */
1069: protected static void formatYearNumber(long year, StringBuffer buff) {
1070: // start with minus sign for dates prior to 1 C.E.
1071: if (year <= 0) {
1072: buff.append('-');
1073: year = -(year - 1);
1074: }
1075:
1076: // add padding if needed to bring to length of four
1077: if (year < 1000) {
1078: buff.append('0');
1079:
1080: if (year < 100) {
1081: buff.append('0');
1082:
1083: if (year < 10) {
1084: buff.append('0');
1085: }
1086: }
1087: }
1088:
1089: // finish by converting the actual year number
1090: buff.append(year);
1091: }
1092:
1093: /**
1094: * Format a positive number as two digits. This uses an optional leading
1095: * zero digit for values less than ten.
1096: *
1097: * @param value number to be formatted (<code>0</code> to <code>99</code>)
1098: * @param buff text formatting buffer
1099: */
1100: protected static void formatTwoDigits(int value, StringBuffer buff) {
1101: if (value < 10) {
1102: buff.append('0');
1103: }
1104:
1105: buff.append(value);
1106: }
1107:
1108: /**
1109: * Format time in milliseconds to year number. The resulting year number
1110: * format is consistent with W3C XML Schema definitions, using a minimum
1111: * of four digits padded with zeros if necessary. A leading minus sign is
1112: * included for years prior to 1 C.E.
1113: *
1114: * @param value time in milliseconds to be converted (from 1 C.E.)
1115: * @param buff text formatting buffer
1116: */
1117: protected static void formatYear(long value, StringBuffer buff) {
1118: // find the actual year and month number; this uses a integer arithmetic
1119: // conversion based on Baum, first making the millisecond count
1120: // relative to March 1 of the year 0 C.E., then using simple arithmetic
1121: // operations to compute century, year, and month; it's slightly
1122: // different for pre-C.E. values because of Java's handling of divisions.
1123: long time = value + (306 * LMSPERDAY) + ((LMSPERDAY * 3) / 4);
1124: long century = time / MSPERCENTURY; // count of centuries
1125: long adjusted = time + ((century - (century / 4)) * MSPERDAY);
1126: int year = (int) (adjusted / MSPERAVGYEAR); // year in March 1 terms
1127:
1128: if (adjusted < 0) {
1129: year--;
1130: }
1131:
1132: long yms = (adjusted + (LMSPERDAY / 4))
1133: - (((year * 365) + (year / 4)) * LMSPERDAY);
1134: int yday = (int) (yms / LMSPERDAY); // day number in year
1135: int month = ((5 * yday) + 456) / 153; // (biased) month number
1136:
1137: if (month > 12) { // convert start of year
1138: year++;
1139: }
1140:
1141: // format year to text
1142: formatYearNumber(year, buff);
1143: }
1144:
1145: /**
1146: * Format time in milliseconds to year number and month number. The
1147: * resulting year number format is consistent with W3C XML Schema
1148: * definitions, using a minimum of four digits for the year and exactly
1149: * two digits for the month.
1150: *
1151: * @param value time in milliseconds to be converted (from 1 C.E.)
1152: * @param buff text formatting buffer
1153: *
1154: * @return number of milliseconds into month
1155: */
1156: protected static long formatYearMonth(long value, StringBuffer buff) {
1157: // find the actual year and month number; this uses a integer arithmetic
1158: // conversion based on Baum, first making the millisecond count
1159: // relative to March 1 of the year 0 C.E., then using simple arithmetic
1160: // operations to compute century, year, and month; it's slightly
1161: // different for pre-C.E. values because of Java's handling of divisions.
1162: long time = value + (306 * LMSPERDAY) + ((LMSPERDAY * 3) / 4);
1163: long century = time / MSPERCENTURY; // count of centuries
1164: long adjusted = time + ((century - (century / 4)) * MSPERDAY);
1165: int year = (int) (adjusted / MSPERAVGYEAR); // year in March 1 terms
1166:
1167: if (adjusted < 0) {
1168: year--;
1169: }
1170:
1171: long yms = (adjusted + (LMSPERDAY / 4))
1172: - (((year * 365) + (year / 4)) * LMSPERDAY);
1173: int yday = (int) (yms / LMSPERDAY); // day number in year
1174:
1175: if (yday == 0) { // special for negative
1176:
1177: boolean bce = year < 0;
1178:
1179: if (bce) {
1180: year--;
1181: }
1182:
1183: int dcnt = ((year % 4) == 0) ? 366 : 365;
1184:
1185: if (!bce) {
1186: year--;
1187: }
1188:
1189: yms += (dcnt * LMSPERDAY);
1190: yday += dcnt;
1191: }
1192:
1193: int month = ((5 * yday) + 456) / 153; // (biased) month number
1194: long rem = yms - BIAS_MONTHMS[month] - LMSPERDAY; // ms into month
1195:
1196: if (month > 12) { // convert start of year
1197: year++;
1198: month -= 12;
1199: }
1200:
1201: // format year and month as text
1202: formatYearNumber(year, buff);
1203: buff.append('-');
1204: formatTwoDigits(month, buff);
1205:
1206: // return extra milliseconds into month
1207: return rem;
1208: }
1209:
1210: /**
1211: * Format time in milliseconds to year number, month number, and day
1212: * number. The resulting year number format is consistent with W3C XML
1213: * Schema definitions, using a minimum of four digits for the year and
1214: * exactly two digits each for the month and day.
1215: *
1216: * @param value time in milliseconds to be converted (from 1 C.E.)
1217: * @param buff text formatting buffer
1218: *
1219: * @return number of milliseconds into day
1220: */
1221: protected static int formatYearMonthDay(long value,
1222: StringBuffer buff) {
1223: // convert year and month
1224: long extra = formatYearMonth(value, buff);
1225:
1226: // append the day of month
1227: int day = (int) (extra / MSPERDAY) + 1;
1228: buff.append('-');
1229: formatTwoDigits(day, buff);
1230:
1231: // return excess of milliseconds into day
1232: return (int) (extra % MSPERDAY);
1233: }
1234:
1235: /**
1236: * Serialize time to general gYear text. Date values are formatted in W3C
1237: * XML Schema standard format as CCYY, with optional leading sign included
1238: * if necessary.
1239: *
1240: * @param time time to be converted, as milliseconds from January 1, 1970
1241: *
1242: * @return converted gYear text
1243: *
1244: * @throws IllegalArgumentException on conversion error
1245: */
1246: public static String serializeYear(long time)
1247: throws IllegalArgumentException {
1248: StringBuffer buff = new StringBuffer(6);
1249: formatYear(time + TIME_BASE, buff);
1250:
1251: return buff.toString();
1252: }
1253:
1254: /**
1255: * Serialize date to general gYear text. Date values are formatted in W3C
1256: * XML Schema standard format as CCYY, with optional leading sign included
1257: * if necessary.
1258: *
1259: * @param date date to be converted
1260: *
1261: * @return converted gYear text
1262: *
1263: * @throws IllegalArgumentException on conversion error
1264: */
1265: public static String serializeYear(Date date)
1266: throws IllegalArgumentException {
1267: return serializeYear(date.getTime());
1268: }
1269:
1270: /**
1271: * Serialize time to general gYearMonth text. Date values are formatted in
1272: * W3C XML Schema standard format as CCYY-MM, with optional leading sign
1273: * included if necessary.
1274: *
1275: * @param time time to be converted, as milliseconds from January 1, 1970
1276: *
1277: * @return converted gYearMonth text
1278: *
1279: * @throws IllegalArgumentException on conversion error
1280: */
1281: public static String serializeYearMonth(long time)
1282: throws IllegalArgumentException {
1283: StringBuffer buff = new StringBuffer(12);
1284: formatYearMonth(time + TIME_BASE, buff);
1285:
1286: return buff.toString();
1287: }
1288:
1289: /**
1290: * Serialize date to general gYearMonth text. Date values are formatted in
1291: * W3C XML Schema standard format as CCYY-MM, with optional leading sign
1292: * included if necessary.
1293: *
1294: * @param date date to be converted
1295: *
1296: * @return converted gYearMonth text
1297: *
1298: * @throws IllegalArgumentException on conversion error
1299: */
1300: public static String serializeYearMonth(Date date)
1301: throws IllegalArgumentException {
1302: return serializeYearMonth(date.getTime());
1303: }
1304:
1305: /**
1306: * Serialize time to general date text. Date values are formatted in W3C
1307: * XML Schema standard format as CCYY-MM-DD, with optional leading sign
1308: * included if necessary.
1309: *
1310: * @param time time to be converted, as milliseconds from January 1, 1970
1311: *
1312: * @return converted date text
1313: *
1314: * @throws IllegalArgumentException on conversion error
1315: */
1316: public static String serializeDate(long time)
1317: throws IllegalArgumentException {
1318: StringBuffer buff = new StringBuffer(12);
1319: formatYearMonthDay(time + TIME_BASE, buff);
1320:
1321: return buff.toString();
1322: }
1323:
1324: /**
1325: * Serialize date to general date text. Date values are formatted in W3C
1326: * XML Schema standard format as CCYY-MM-DD, with optional leading sign
1327: * included if necessary.
1328: *
1329: * @param date date to be converted
1330: *
1331: * @return converted date text
1332: *
1333: * @throws IllegalArgumentException on conversion error
1334: */
1335: public static String serializeDate(Date date)
1336: throws IllegalArgumentException {
1337: return serializeDate(date.getTime());
1338: }
1339:
1340: /**
1341: * Serialize SQL date to general date text. Date values are formatted in
1342: * W3C XML Schema standard format as CCYY-MM-DD, with optional leading
1343: * sign included if necessary.
1344: *
1345: * @param date date to be converted
1346: *
1347: * @return converted date text
1348: *
1349: * @throws IllegalArgumentException on conversion error
1350: */
1351: public static String serializeSqlDate(java.sql.Date date)
1352: throws IllegalArgumentException {
1353: // convert time assuming maximum daylight time difference of two hours
1354: long time = date.getTime();
1355: time -= (TimeZone.getDefault().getRawOffset() - (MSPERHOUR * 2));
1356:
1357: return serializeDate(time);
1358: }
1359:
1360: /**
1361: * Serialize time to general time text in buffer. Time values are formatted
1362: * in W3C XML Schema standard format as hh:mm:ss, with optional trailing
1363: * seconds decimal, as necessary. This form uses a supplied buffer to
1364: * support flexible use, including with dateTime combination values.
1365: *
1366: * @param time time to be converted, as milliseconds in day
1367: * @param buff buffer for appending time text
1368: *
1369: * @throws IllegalArgumentException on conversion error
1370: */
1371: public static void serializeTime(int time, StringBuffer buff)
1372: throws IllegalArgumentException {
1373: // append the hour, minute, and second
1374: formatTwoDigits(time / MSPERHOUR, buff);
1375: time = time % MSPERHOUR;
1376: buff.append(':');
1377: formatTwoDigits(time / MSPERMINUTE, buff);
1378: time = time % MSPERMINUTE;
1379: buff.append(':');
1380: formatTwoDigits(time / 1000, buff);
1381: time = time % 1000;
1382:
1383: // check if decimals needed on second
1384: if (time > 0) {
1385: buff.append('.');
1386: buff.append(time / 100);
1387: time = time % 100;
1388:
1389: if (time > 0) {
1390: buff.append(time / 10);
1391: time = time % 10;
1392:
1393: if (time > 0) {
1394: buff.append(time);
1395: }
1396: }
1397: }
1398: }
1399:
1400: /**
1401: * Serialize time to general dateTime text. Date values are formatted in
1402: * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with optional
1403: * leading sign and trailing seconds decimal, as necessary.
1404: *
1405: * @param time time to be converted, as milliseconds from January 1, 1970
1406: * @param zone flag for trailing 'Z' to be appended to indicate UTC
1407: *
1408: * @return converted dateTime text
1409: *
1410: * @throws IllegalArgumentException on conversion error
1411: */
1412: public static String serializeDateTime(long time, boolean zone)
1413: throws IllegalArgumentException {
1414: // start with the year, month, and day
1415: StringBuffer buff = new StringBuffer(25);
1416: int extra = formatYearMonthDay(time + TIME_BASE, buff);
1417:
1418: // append the time for full form
1419: buff.append('T');
1420: serializeTime(extra, buff);
1421:
1422: // return full text with optional trailing zone indicator
1423: if (zone) {
1424: buff.append('Z');
1425: }
1426:
1427: return buff.toString();
1428: }
1429:
1430: /**
1431: * Serialize time to general dateTime text. This method is provided for
1432: * backward compatibility. It generates the dateTime text without the
1433: * trailing 'Z' to indicate UTC.
1434: *
1435: * @param time time to be converted, as milliseconds from January 1, 1970
1436: *
1437: * @return converted dateTime text
1438: *
1439: * @throws IllegalArgumentException on conversion error
1440: */
1441: public static String serializeDateTime(long time)
1442: throws IllegalArgumentException {
1443: return serializeDateTime(time, false);
1444: }
1445:
1446: /**
1447: * Serialize date to general dateTime text. Date values are formatted in
1448: * W3C XML Schema standard format as CCYY-MM-DDThh:mm:ss, with optional
1449: * leading sign and trailing seconds decimal, as necessary.
1450: *
1451: * @param date date to be converted
1452: *
1453: * @return converted dateTime text
1454: *
1455: * @throws IllegalArgumentException on conversion error
1456: */
1457: public static String serializeDateTime(Date date)
1458: throws IllegalArgumentException {
1459: return serializeDateTime(date.getTime(), false);
1460: }
1461:
1462: /**
1463: * Serialize timestamp to general dateTime text. Timestamp values are
1464: * represented in the same way as regular dates, but allow more precision
1465: * in the fractional second value (down to nanoseconds).
1466: *
1467: * @param stamp timestamp to be converted
1468: *
1469: * @return converted dateTime text
1470: *
1471: * @throws IllegalArgumentException on conversion error
1472: */
1473: public static String serializeTimestamp(Timestamp stamp)
1474: throws IllegalArgumentException {
1475: // check for nanosecond value to be included
1476: int nano = stamp.getNanos();
1477:
1478: if (nano > 0) {
1479: // convert the number of nanoseconds to text
1480: String value = serializeInt(nano);
1481:
1482: // pad with leading zeros if less than 9 digits
1483: StringBuffer digits = new StringBuffer(9);
1484:
1485: if (value.length() < 9) {
1486: int lead = 9 - value.length();
1487:
1488: for (int i = 0; i < lead; i++) {
1489: digits.append('0');
1490: }
1491: }
1492:
1493: digits.append(value);
1494:
1495: // strip trailing zeros from value
1496: int last = 9;
1497:
1498: while (--last > 0) {
1499: if (digits.charAt(last) != '0') {
1500: break;
1501: }
1502: }
1503:
1504: digits.setLength(last);
1505:
1506: // finish by appending to time with decimal separator
1507: return serializeDateTime(stamp.getTime(), false) + '.'
1508: + digits + 'Z';
1509: } else {
1510: return serializeDateTime(stamp.getTime(), true);
1511: }
1512: }
1513:
1514: /**
1515: * Serialize time to standard text. Time values are formatted in W3C XML
1516: * Schema standard format as hh:mm:ss, with optional trailing seconds
1517: * decimal, as necessary. The standard conversion does not append a time
1518: * zone indication.
1519: *
1520: * @param time time to be converted
1521: *
1522: * @return converted time text
1523: *
1524: * @throws IllegalArgumentException on conversion error
1525: */
1526: public static String serializeSqlTime(Time time)
1527: throws IllegalArgumentException {
1528: StringBuffer buff = new StringBuffer(12);
1529: serializeTime((int) time.getTime(), buff);
1530:
1531: return buff.toString();
1532: }
1533:
1534: /**
1535: * General object comparison method. Don't know why Sun hasn't seen fit to
1536: * include this somewhere, but at least it's easy to write (over and over
1537: * again).
1538: *
1539: * @param a first object to be compared
1540: * @param b second object to be compared
1541: *
1542: * @return <code>true</code> if both objects are <code>null</code>, or if
1543: * <code>a.equals(b)</code>; <code>false</code> otherwise
1544: */
1545: public static boolean isEqual(Object a, Object b) {
1546: return (a == null) ? (b == null) : a.equals(b);
1547: }
1548:
1549: /**
1550: * Find text value in enumeration. This first does a binary search through
1551: * an array of allowed text matches. If a separate array of corresponding
1552: * values is supplied, the value at the matched position is returned;
1553: * otherwise the match index is returned directly.
1554: *
1555: * @param target text to be found in enumeration
1556: * @param enums ordered array of texts included in enumeration
1557: * @param vals array of values to be returned for corresponding text match
1558: * positions (position returned directly if this is
1559: * <code>null</code>)
1560: *
1561: * @return enumeration value for target text
1562: *
1563: * @throws IllegalArgumentException if target text not found in enumeration
1564: */
1565: public static int enumValue(String target, String[] enums,
1566: int[] vals) throws IllegalArgumentException {
1567: int base = 0;
1568: int limit = enums.length - 1;
1569:
1570: while (base <= limit) {
1571: int cur = (base + limit) >> 1;
1572: int diff = target.compareTo(enums[cur]);
1573:
1574: if (diff < 0) {
1575: limit = cur - 1;
1576: } else if (diff > 0) {
1577: base = cur + 1;
1578: } else if (vals != null) {
1579: return vals[cur];
1580: } else {
1581: return cur;
1582: }
1583: }
1584:
1585: throw new IllegalArgumentException("Target value \"" + target
1586: + "\" not found in enumeration");
1587: }
1588:
1589: /**
1590: * Decode a chunk of data from base64 encoding. The length of a chunk is
1591: * always 4 characters in the base64 representation, but may be 1, 2, or 3
1592: * bytes of data, as determined by whether there are any pad characters at
1593: * the end of the base64 representation
1594: *
1595: * @param base starting offset within base64 character array
1596: * @param chrs character array for base64 text representation
1597: * @param fill starting offset within byte data array
1598: * @param byts byte data array
1599: *
1600: * @return number of decoded bytes
1601: *
1602: * @throws IllegalArgumentException if invalid character in base64
1603: * representation
1604: */
1605: private static int decodeChunk(int base, char[] chrs, int fill,
1606: byte[] byts) throws IllegalArgumentException {
1607: // find the byte count to be decoded
1608: int length = 3;
1609:
1610: if (chrs[base + 3] == PAD_CHAR) {
1611: length = 2;
1612:
1613: if (chrs[base + 2] == PAD_CHAR) {
1614: length = 1;
1615: }
1616: }
1617:
1618: // get 6-bit values
1619: int v0 = s_base64Values[chrs[base + 0]];
1620: int v1 = s_base64Values[chrs[base + 1]];
1621: int v2 = s_base64Values[chrs[base + 2]];
1622: int v3 = s_base64Values[chrs[base + 3]];
1623:
1624: // convert and store bytes of data
1625: switch (length) {
1626: case 3:
1627: byts[fill + 2] = (byte) ((v2 << 6) | v3);
1628:
1629: case 2:
1630: byts[fill + 1] = (byte) ((v1 << 4) | (v2 >> 2));
1631:
1632: case 1:
1633: byts[fill] = (byte) ((v0 << 2) | (v1 >> 4));
1634:
1635: break;
1636: }
1637:
1638: return length;
1639: }
1640:
1641: /**
1642: * Parse base64 data from text. This converts the base64 data into a byte
1643: * array of the appopriate length. In keeping with the recommendations,
1644: *
1645: * @param text text to be parsed (may include extra characters)
1646: *
1647: * @return byte array of data
1648: *
1649: * @throws IllegalArgumentException if invalid character in base64
1650: * representation
1651: */
1652: public static byte[] parseBase64(String text)
1653: throws IllegalArgumentException {
1654: // convert raw text to base64 character array
1655: char[] chrs = new char[text.length()];
1656: int length = 0;
1657:
1658: for (int i = 0; i < text.length(); i++) {
1659: char chr = text.charAt(i);
1660:
1661: if ((chr < 128) && (s_base64Values[chr] >= 0)) {
1662: chrs[length++] = chr;
1663: }
1664: }
1665:
1666: // check the text length
1667: if ((length % 4) != 0) {
1668: throw new IllegalArgumentException(
1669: "Text length for base64 must be a multiple of 4");
1670: } else if (length == 0) {
1671: return new byte[0];
1672: }
1673:
1674: // find corresponding byte count for data
1675: int blength = length / 4 * 3;
1676:
1677: if (chrs[length - 1] == PAD_CHAR) {
1678: blength--;
1679:
1680: if (chrs[length - 2] == PAD_CHAR) {
1681: blength--;
1682: }
1683: }
1684:
1685: // convert text to actual bytes of data
1686: byte[] byts = new byte[blength];
1687: int fill = 0;
1688:
1689: for (int i = 0; i < length; i += 4) {
1690: fill += decodeChunk(i, chrs, fill, byts);
1691: }
1692:
1693: if (fill != blength) {
1694: throw new IllegalArgumentException(
1695: "Embedded padding characters in byte64 text");
1696: }
1697:
1698: return byts;
1699: }
1700:
1701: /**
1702: * Parse base64 data from text. This converts the base64 data into a byte
1703: * array of the appopriate length. In keeping with the recommendations,
1704: *
1705: * @param text text to be parsed (may be null, or include extra characters)
1706: *
1707: * @return byte array of data
1708: *
1709: * @throws IllegalArgumentException if invalid character in base64
1710: * representation
1711: */
1712: public static byte[] deserializeBase64(String text)
1713: throws IllegalArgumentException {
1714: if (text == null) {
1715: return null;
1716: } else {
1717: return parseBase64(text);
1718: }
1719: }
1720:
1721: /**
1722: * Encode a chunk of data to base64 encoding. Converts the next three bytes
1723: * of data into four characters of text representation, using padding at
1724: * the end of less than three bytes of data remain.
1725: *
1726: * @param base starting offset within byte array
1727: * @param byts byte data array
1728: * @param buff buffer for encoded text
1729: */
1730: public static void encodeChunk(int base, byte[] byts,
1731: StringBuffer buff) {
1732: // get actual byte data length to be encoded
1733: int length = 3;
1734:
1735: if ((base + length) > byts.length) {
1736: length = byts.length - base;
1737: }
1738:
1739: // convert up to three bytes of data to four characters of text
1740: int b0 = byts[base];
1741: int value = (b0 >> 2) & 0x3F;
1742: buff.append(s_base64Chars[value]);
1743:
1744: if (length > 1) {
1745: int b1 = byts[base + 1];
1746: value = ((b0 & 3) << 4) + ((b1 >> 4) & 0x0F);
1747: buff.append(s_base64Chars[value]);
1748:
1749: if (length > 2) {
1750: int b2 = byts[base + 2];
1751: value = ((b1 & 0x0F) << 2) + ((b2 >> 6) & 3);
1752: buff.append(s_base64Chars[value]);
1753: value = b2 & 0x3F;
1754: buff.append(s_base64Chars[value]);
1755: } else {
1756: value = (b1 & 0x0F) << 2;
1757: buff.append(s_base64Chars[value]);
1758: buff.append(PAD_CHAR);
1759: }
1760: } else {
1761: value = (b0 & 3) << 4;
1762: buff.append(s_base64Chars[value]);
1763: buff.append(PAD_CHAR);
1764: buff.append(PAD_CHAR);
1765: }
1766: }
1767:
1768: /**
1769: * Serialize byte array to base64 text. In keeping with the specification,
1770: * this adds a line break every 76 characters in the encoded
1771: * representation.
1772: *
1773: * @param byts byte data array
1774: *
1775: * @return base64 encoded text
1776: */
1777: public static String serializeBase64(byte[] byts) {
1778: StringBuffer buff = new StringBuffer((byts.length + 2) / 3 * 4);
1779:
1780: for (int i = 0; i < byts.length; i += 3) {
1781: encodeChunk(i, byts, buff);
1782:
1783: if ((i > 0) && ((i % 57) == 0) && ((i + 3) < byts.length)) {
1784: buff.append("\r\n");
1785: }
1786: }
1787:
1788: return buff.toString();
1789: }
1790:
1791: /**
1792: * Factory method to create a <code>java.util.ArrayList</code> as the
1793: * implementation of a <code>java.util.List</code>.
1794: *
1795: * @return new <code>java.util.ArrayList</code>
1796: */
1797: public static List arrayListFactory() {
1798: return new ArrayList();
1799: }
1800: }
|