001: /*
002:
003: Derby - Class org.apache.derby.iapi.types.SQLDate
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.types;
023:
024: import org.apache.derby.iapi.types.SQLInteger;
025:
026: import org.apache.derby.iapi.reference.SQLState;
027:
028: import org.apache.derby.iapi.services.io.ArrayInputStream;
029:
030: import org.apache.derby.iapi.error.StandardException;
031:
032: import org.apache.derby.iapi.db.DatabaseContext;
033: import org.apache.derby.iapi.types.DataValueDescriptor;
034: import org.apache.derby.iapi.types.TypeId;
035:
036: import org.apache.derby.iapi.types.NumberDataValue;
037: import org.apache.derby.iapi.types.DateTimeDataValue;
038:
039: import org.apache.derby.iapi.services.context.ContextService;
040:
041: import org.apache.derby.iapi.services.io.StoredFormatIds;
042:
043: import org.apache.derby.iapi.services.sanity.SanityManager;
044:
045: import org.apache.derby.iapi.types.DataType;
046:
047: import org.apache.derby.iapi.services.cache.ClassSize;
048: import org.apache.derby.iapi.services.i18n.LocaleFinder;
049: import org.apache.derby.iapi.util.StringUtil;
050:
051: import java.sql.Date;
052: import java.sql.Time;
053: import java.sql.Timestamp;
054: import java.sql.Types;
055: import java.sql.PreparedStatement;
056:
057: import java.util.Calendar;
058: import java.util.GregorianCalendar;
059:
060: import java.io.ObjectOutput;
061: import java.io.ObjectInput;
062: import java.io.IOException;
063:
064: import java.sql.ResultSet;
065: import java.sql.SQLException;
066:
067: import java.text.DateFormat;
068: import java.text.ParseException;
069:
070: /**
071: * This contains an instance of a SQL Date.
072: * <p>
073: * The date is stored as int (year << 16 + month << 8 + day)
074: * Null is represented by an encodedDate value of 0.
075: * Some of the static methods in this class are also used by SQLTime and SQLTimestamp
076: * so check those classes if you change the date encoding
077: *
078: * PERFORMANCE OPTIMIZATION:
079: * The java.sql.Date object is only instantiated when needed
080: * do to the overhead of Date.valueOf(), etc. methods.
081: */
082:
083: public final class SQLDate extends DataType implements
084: DateTimeDataValue {
085:
086: private int encodedDate; //year << 16 + month << 8 + day
087:
088: // The cached value.toString()
089: private String valueString;
090:
091: private static final int BASE_MEMORY_USAGE = ClassSize
092: .estimateBaseFromCatalog(SQLDate.class);
093:
094: public int estimateMemoryUsage() {
095: return BASE_MEMORY_USAGE
096: + ClassSize.estimateMemoryUsage(valueString);
097: } // end of estimateMemoryUsage
098:
099: int getEncodedDate() {
100: return encodedDate;
101: }
102:
103: /*
104: ** DataValueDescriptor interface
105: ** (mostly implemented in DataType)
106: */
107:
108: public String getString() {
109: //format is [yyy]y-mm-dd e.g. 1-01-01, 9999-99-99
110: if (!isNull()) {
111: if (valueString == null) {
112: valueString = encodedDateToString(encodedDate);
113: }
114: return valueString;
115: } else {
116: if (SanityManager.DEBUG) {
117: if (valueString != null) {
118: SanityManager
119: .THROWASSERT("valueString expected to be null, not "
120: + valueString);
121: }
122: }
123: return null;
124: }
125: }
126:
127: /**
128: getTimestamp returns a timestamp with the date value
129: time is set to 00:00:00.0
130: */
131: public Timestamp getTimestamp(Calendar cal) {
132: if (isNull()) {
133: return null;
134: } else
135: // date is converted to a timestamp filling the time in with 00:00:00
136: return newTimestamp(cal);
137: }
138:
139: private long getTimeInMillis(Calendar cal) {
140: if (cal == null)
141: cal = new GregorianCalendar();
142: cal.clear();
143: cal.set(getYear(encodedDate), getMonth(encodedDate) - 1,
144: getDay(encodedDate));
145: return cal.getTime().getTime();
146: }
147:
148: private Timestamp newTimestamp(java.util.Calendar cal) {
149: return new Timestamp(getTimeInMillis(cal));
150: }
151:
152: /**
153: getObject returns the date value
154:
155: */
156: public Object getObject() {
157: return getDate((Calendar) null);
158: }
159:
160: public int getLength() {
161: return 4;
162: }
163:
164: /* this is for DataType's error generator */
165: public String getTypeName() {
166: return "DATE";
167: }
168:
169: /*
170: * Storable interface, implies Externalizable, TypedFormat
171: */
172:
173: /**
174: Return my format identifier.
175:
176: @see org.apache.derby.iapi.services.io.TypedFormat#getTypeFormatId
177: */
178: public int getTypeFormatId() {
179: return StoredFormatIds.SQL_DATE_ID;
180: }
181:
182: /**
183: @exception IOException error writing data
184:
185: */
186: public void writeExternal(ObjectOutput out) throws IOException {
187:
188: if (SanityManager.DEBUG)
189: SanityManager
190: .ASSERT(!isNull(),
191: "writeExternal() is not supposed to be called for null values.");
192:
193: out.writeInt(encodedDate);
194: }
195:
196: /**
197: * @see java.io.Externalizable#readExternal
198: *
199: * @exception IOException Thrown on error reading the object
200: */
201: public void readExternal(ObjectInput in) throws IOException {
202: encodedDate = in.readInt();
203:
204: // reset cached string values
205: valueString = null;
206: }
207:
208: public void readExternalFromArray(ArrayInputStream in)
209: throws IOException {
210: encodedDate = in.readInt();
211:
212: // reset cached string values
213: valueString = null;
214: }
215:
216: /*
217: * DataValueDescriptor interface
218: */
219:
220: /** @see DataValueDescriptor#getClone */
221: public DataValueDescriptor getClone() {
222: // Call constructor with all of our info
223: return new SQLDate(encodedDate);
224: }
225:
226: /**
227: * @see DataValueDescriptor#getNewNull
228: */
229: public DataValueDescriptor getNewNull() {
230: return new SQLDate();
231: }
232:
233: /**
234: * @see org.apache.derby.iapi.services.io.Storable#restoreToNull
235: *
236: */
237:
238: public void restoreToNull() {
239: // clear encodedDate
240: encodedDate = 0;
241:
242: // clear cached valueString
243: valueString = null;
244: }
245:
246: /*
247: * DataValueDescriptor interface
248: */
249:
250: /**
251: * @see DataValueDescriptor#setValueFromResultSet
252: *
253: * @exception SQLException Thrown on error
254: */
255: public void setValueFromResultSet(ResultSet resultSet,
256: int colNumber, boolean isNullable) throws SQLException,
257: StandardException {
258: setValue(resultSet.getDate(colNumber), (Calendar) null);
259: }
260:
261: /**
262: * Orderable interface
263: *
264: *
265: * @see org.apache.derby.iapi.types.Orderable
266: *
267: * @exception StandardException thrown on failure
268: */
269: public int compare(DataValueDescriptor other)
270: throws StandardException {
271: /* Use compare method from dominant type, negating result
272: * to reflect flipping of sides.
273: */
274: if (typePrecedence() < other.typePrecedence()) {
275: return -(other.compare(this ));
276: }
277:
278: boolean this Null, otherNull;
279:
280: this Null = this .isNull();
281: otherNull = other.isNull();
282:
283: /*
284: * thisNull otherNull return
285: * T T 0 (this == other)
286: * F T -1 (this < other)
287: * T F 1 (this > other)
288: */
289: if (this Null || otherNull) {
290: if (!this Null) // otherNull must be true
291: return -1;
292: if (!otherNull) // thisNull must be true
293: return 1;
294: return 0;
295: }
296:
297: /*
298: Neither are null compare them
299: */
300:
301: int comparison;
302: /* get the comparison date values */
303: int otherVal = 0;
304:
305: /* if the argument is another SQLDate
306: * get the encodedDate
307: */
308: if (other instanceof SQLDate) {
309: otherVal = ((SQLDate) other).encodedDate;
310: } else {
311: /* O.K. have to do it the hard way and calculate the numeric value
312: * from the value
313: */
314: otherVal = SQLDate.computeEncodedDate(other
315: .getDate(new GregorianCalendar()));
316: }
317: if (encodedDate > otherVal)
318: comparison = 1;
319: else if (encodedDate < otherVal)
320: comparison = -1;
321: else
322: comparison = 0;
323:
324: return comparison;
325: }
326:
327: /**
328: @exception StandardException thrown on error
329: */
330: public boolean compare(int op, DataValueDescriptor other,
331: boolean orderedNulls, boolean unknownRV)
332: throws StandardException {
333: if (!orderedNulls) // nulls are unordered
334: {
335: if (this .isNull() || other.isNull())
336: return unknownRV;
337: }
338:
339: /* Do the comparison */
340: return super .compare(op, other, orderedNulls, unknownRV);
341: }
342:
343: /*
344: ** Class interface
345: */
346:
347: /*
348: ** Constructors
349: */
350:
351: /** no-arg constructor required by Formattable */
352: public SQLDate() {
353: }
354:
355: public SQLDate(Date value) throws StandardException {
356: parseDate(value);
357: }
358:
359: private void parseDate(java.util.Date value)
360: throws StandardException {
361: encodedDate = computeEncodedDate(value);
362: }
363:
364: private SQLDate(int encodedDate) {
365: this .encodedDate = encodedDate;
366: }
367:
368: /**
369: * Construct a date from a string. The allowed date formats are:
370: *<ol>
371: *<li>ISO: yyyy-mm-dd
372: *<li>IBM USA standard: mm/dd/yyyy
373: *<li>IBM European standard: dd.mm.yyyy
374: *</ol>
375: * Trailing blanks may be included; leading zeros may be omitted from the month and day portions.
376: *
377: * @param dateStr
378: * @param isJdbcEscape if true then only the JDBC date escape syntax is allowed
379: * @param localeFinder
380: *
381: * @exception Standard exception if the syntax is invalid or the value is out of range.
382: */
383: public SQLDate(String dateStr, boolean isJdbcEscape,
384: LocaleFinder localeFinder) throws StandardException {
385: parseDate(dateStr, isJdbcEscape, localeFinder, (Calendar) null);
386: }
387:
388: /**
389: * Construct a date from a string. The allowed date formats are:
390: *<ol>
391: *<li>ISO: yyyy-mm-dd
392: *<li>IBM USA standard: mm/dd/yyyy
393: *<li>IBM European standard: dd.mm.yyyy
394: *</ol>
395: * Trailing blanks may be included; leading zeros may be omitted from the month and day portions.
396: *
397: * @param dateStr
398: * @param isJdbcEscape if true then only the JDBC date escape syntax is allowed
399: * @param localeFinder
400: *
401: * @exception Standard exception if the syntax is invalid or the value is out of range.
402: */
403: public SQLDate(String dateStr, boolean isJdbcEscape,
404: LocaleFinder localeFinder, Calendar cal)
405: throws StandardException {
406: parseDate(dateStr, isJdbcEscape, localeFinder, cal);
407: }
408:
409: static final char ISO_SEPARATOR = '-';
410: private static final char[] ISO_SEPARATOR_ONLY = { ISO_SEPARATOR };
411: private static final char IBM_USA_SEPARATOR = '/';
412: private static final char[] IBM_USA_SEPARATOR_ONLY = { IBM_USA_SEPARATOR };
413: private static final char IBM_EUR_SEPARATOR = '.';
414: private static final char[] IBM_EUR_SEPARATOR_ONLY = { IBM_EUR_SEPARATOR };
415: private static final char[] END_OF_STRING = { (char) 0 };
416:
417: private void parseDate(String dateStr, boolean isJdbcEscape,
418: LocaleFinder localeFinder, Calendar cal)
419: throws StandardException {
420: boolean validSyntax = true;
421: DateTimeParser parser = new DateTimeParser(dateStr);
422: int year = 0;
423: int month = 0;
424: int day = 0;
425: StandardException thrownSE = null;
426:
427: try {
428: switch (parser.nextSeparator()) {
429: case ISO_SEPARATOR:
430: encodedDate = SQLTimestamp.parseDateOrTimestamp(parser,
431: false)[0];
432: valueString = parser.getTrimmedString();
433: return;
434:
435: case IBM_USA_SEPARATOR:
436: if (isJdbcEscape) {
437: validSyntax = false;
438: break;
439: }
440: month = parser.parseInt(2, true,
441: IBM_USA_SEPARATOR_ONLY, false);
442: day = parser.parseInt(2, true, IBM_USA_SEPARATOR_ONLY,
443: false);
444: year = parser.parseInt(4, false, END_OF_STRING, false);
445: break;
446:
447: case IBM_EUR_SEPARATOR:
448: if (isJdbcEscape) {
449: validSyntax = false;
450: break;
451: }
452: day = parser.parseInt(2, true, IBM_EUR_SEPARATOR_ONLY,
453: false);
454: month = parser.parseInt(2, true,
455: IBM_EUR_SEPARATOR_ONLY, false);
456: year = parser.parseInt(4, false, END_OF_STRING, false);
457: break;
458:
459: default:
460: validSyntax = false;
461: }
462: } catch (StandardException se) {
463: validSyntax = false;
464: thrownSE = se;
465: }
466: if (validSyntax) {
467: valueString = parser.checkEnd();
468: encodedDate = computeEncodedDate(year, month, day);
469: } else {
470: // See if it is a localized date or timestamp.
471: dateStr = StringUtil.trimTrailing(dateStr);
472: DateFormat dateFormat = null;
473: if (localeFinder == null)
474: dateFormat = DateFormat.getDateInstance();
475: else if (cal == null)
476: dateFormat = localeFinder.getDateFormat();
477: else
478: dateFormat = (DateFormat) localeFinder.getDateFormat()
479: .clone();
480: if (cal != null)
481: dateFormat.setCalendar(cal);
482: try {
483: encodedDate = computeEncodedDate(dateFormat
484: .parse(dateStr), cal);
485: } catch (ParseException pe) {
486: // Maybe it is a localized timestamp
487: try {
488: encodedDate = SQLTimestamp.parseLocalTimestamp(
489: dateStr, localeFinder, cal)[0];
490: } catch (ParseException pe2) {
491: if (thrownSE != null)
492: throw thrownSE;
493: throw StandardException
494: .newException(SQLState.LANG_DATE_SYNTAX_EXCEPTION);
495: }
496: }
497: valueString = dateStr;
498: }
499: } // end of parseDate
500:
501: /**
502: * Set the value from a correctly typed Date object.
503: * @throws StandardException
504: */
505: void setObject(Object theValue) throws StandardException {
506: setValue((Date) theValue);
507: }
508:
509: protected void setFrom(DataValueDescriptor theValue)
510: throws StandardException {
511:
512: // Same format means same type SQLDate
513: if (theValue instanceof SQLDate) {
514: restoreToNull();
515: encodedDate = ((SQLDate) theValue).encodedDate;
516: } else {
517: Calendar cal = new GregorianCalendar();
518: setValue(theValue.getDate(cal), cal);
519: }
520: }
521:
522: /**
523: @see DateTimeDataValue#setValue
524:
525: */
526: public void setValue(Date value, Calendar cal)
527: throws StandardException {
528: restoreToNull();
529: encodedDate = computeEncodedDate((java.util.Date) value, cal);
530: }
531:
532: /**
533: @see DateTimeDataValue#setValue
534:
535: */
536: public void setValue(Timestamp value, Calendar cal)
537: throws StandardException {
538: restoreToNull();
539: encodedDate = computeEncodedDate((java.util.Date) value, cal);
540: }
541:
542: public void setValue(String theValue) throws StandardException {
543: restoreToNull();
544:
545: if (theValue != null) {
546: DatabaseContext databaseContext = (DatabaseContext) ContextService
547: .getContext(DatabaseContext.CONTEXT_ID);
548: parseDate(theValue, false, (databaseContext == null) ? null
549: : databaseContext.getDatabase(), (Calendar) null);
550: }
551: }
552:
553: /*
554: ** SQL Operators
555: */
556:
557: /**
558: * @see DateTimeDataValue#getYear
559: *
560: * @exception StandardException Thrown on error
561: */
562: public NumberDataValue getYear(NumberDataValue result)
563: throws StandardException {
564: if (SanityManager.DEBUG) {
565: SanityManager.ASSERT(!isNull(), "getYear called on a null");
566: }
567: return SQLDate.setSource(getYear(encodedDate), result);
568: }
569:
570: /**
571: * @see DateTimeDataValue#getMonth
572: *
573: * @exception StandardException Thrown on error
574: */
575: public NumberDataValue getMonth(NumberDataValue result)
576: throws StandardException {
577: if (SanityManager.DEBUG) {
578: SanityManager
579: .ASSERT(!isNull(), "getMonth called on a null");
580: }
581: return SQLDate.setSource(getMonth(encodedDate), result);
582: }
583:
584: /**
585: * @see DateTimeDataValue#getDate
586: *
587: * @exception StandardException Thrown on error
588: */
589: public NumberDataValue getDate(NumberDataValue result)
590: throws StandardException {
591: if (SanityManager.DEBUG) {
592: SanityManager.ASSERT(!isNull(), "getDate called on a null");
593: }
594: return SQLDate.setSource(getDay(encodedDate), result);
595: }
596:
597: /**
598: * @see DateTimeDataValue#getHours
599: *
600: * @exception StandardException Thrown on error
601: */
602: public NumberDataValue getHours(NumberDataValue result)
603: throws StandardException {
604: if (SanityManager.DEBUG) {
605: SanityManager.ASSERT(!isNull(), "getHours called on null.");
606: }
607: throw StandardException.newException(
608: SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, "getHours",
609: "Date");
610: }
611:
612: /**
613: * @see DateTimeDataValue#getMinutes
614: *
615: * @exception StandardException Thrown on error
616: */
617: public NumberDataValue getMinutes(NumberDataValue result)
618: throws StandardException {
619: if (SanityManager.DEBUG) {
620: SanityManager.ASSERT(!isNull(),
621: "getMinutes called on null.");
622: }
623: throw StandardException.newException(
624: SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, "getMinutes",
625: "Date");
626: }
627:
628: /**
629: * @see DateTimeDataValue#getSeconds
630: *
631: * @exception StandardException Thrown on error
632: */
633: public NumberDataValue getSeconds(NumberDataValue result)
634: throws StandardException {
635: if (SanityManager.DEBUG) {
636: SanityManager.ASSERT(!isNull(),
637: "getSeconds called on null.");
638: }
639: throw StandardException.newException(
640: SQLState.LANG_UNARY_FUNCTION_BAD_TYPE, "getSeconds",
641: "Date");
642: }
643:
644: /*
645: ** String display of value
646: */
647:
648: public String toString() {
649: if (isNull()) {
650: return "NULL";
651: } else {
652: return getDate((Calendar) null).toString();
653: }
654: }
655:
656: /*
657: * Hash code
658: */
659: public int hashCode() {
660: return encodedDate;
661: }
662:
663: /** @see DataValueDescriptor#typePrecedence */
664: public int typePrecedence() {
665: return TypeId.DATE_PRECEDENCE;
666: }
667:
668: /**
669: * Check if the value is null.
670: * encodedDate is 0 if the value is null
671: *
672: * @return Whether or not value is logically null.
673: */
674: public final boolean isNull() {
675: return (encodedDate == 0);
676: }
677:
678: /**
679: * Get the value field. We instantiate the field
680: * on demand.
681: *
682: * @return The value field.
683: */
684: public Date getDate(Calendar cal) {
685: if (encodedDate != 0)
686: return new Date(getTimeInMillis(cal));
687:
688: return null;
689: }
690:
691: /**
692: * Get the year from the encodedDate.
693: *
694: * @param encodedDate the encoded date
695: * @return year value.
696: */
697: static int getYear(int encodedDate) {
698: return (encodedDate >>> 16);
699: }
700:
701: /**
702: * Get the month from the encodedDate.
703: *
704: * @param encodedDate the encoded date
705: * @return month value.
706: */
707: static int getMonth(int encodedDate) {
708: return ((encodedDate >>> 8) & 0x00ff);
709: }
710:
711: /**
712: * Get the day from the encodedDate.
713: *
714: * @param encodedDate the encoded date
715: * @return day value.
716: */
717: static int getDay(int encodedDate) {
718: return (encodedDate & 0x00ff);
719: }
720:
721: /**
722: * computeEncodedDate extracts the year, month and date from
723: * a Calendar value and encodes them as
724: * year << 16 + month << 8 + date
725: * Use this function will help to remember to add 1 to month
726: * which is 0 based in the Calendar class
727: * @param cal the Calendar
728: * @return the encodedDate
729: *
730: * @exception StandardException if the value is out of the DB2 date range
731: */
732: static int computeEncodedDate(Calendar cal)
733: throws StandardException {
734: return computeEncodedDate(cal.get(Calendar.YEAR), cal
735: .get(Calendar.MONTH) + 1, cal.get(Calendar.DATE));
736: }
737:
738: static int computeEncodedDate(int y, int m, int d)
739: throws StandardException {
740: int maxDay = 31;
741: switch (m) {
742: case 4:
743: case 6:
744: case 9:
745: case 11:
746: maxDay = 30;
747: break;
748:
749: case 2:
750: // leap years are every 4 years except for century years not divisble by 400.
751: maxDay = ((y % 4) == 0 && ((y % 100) != 0 || (y % 400) == 0)) ? 29
752: : 28;
753: break;
754: }
755: if (y < 1 || y > 9999 || m < 1 || m > 12 || d < 1 || d > maxDay)
756: throw StandardException
757: .newException(SQLState.LANG_DATE_RANGE_EXCEPTION);
758: return (y << 16) + (m << 8) + d;
759: }
760:
761: /**
762: * Convert a date to the JDBC representation and append it to a string buffer.
763: *
764: * @param year
765: * @param month 1 based (January == 1)
766: * @param day
767: * @param sb The string representation is appended to this StringBuffer
768: */
769: static void dateToString(int year, int month, int day,
770: StringBuffer sb) {
771: String yearStr = Integer.toString(year);
772: for (int i = yearStr.length(); i < 4; i++)
773: sb.append('0');
774: sb.append(yearStr);
775: sb.append(ISO_SEPARATOR);
776:
777: String monthStr = Integer.toString(month);
778: String dayStr = Integer.toString(day);
779: if (monthStr.length() == 1)
780: sb.append('0');
781: sb.append(monthStr);
782: sb.append(ISO_SEPARATOR);
783: if (dayStr.length() == 1)
784: sb.append('0');
785: sb.append(dayStr);
786: } // end of dateToString
787:
788: /**
789: * Get the String version from the encodedDate.
790: *
791: * @return string value.
792: */
793: static String encodedDateToString(int encodedDate) {
794: StringBuffer vstr = new StringBuffer();
795: dateToString(getYear(encodedDate), getMonth(encodedDate),
796: getDay(encodedDate), vstr);
797: return vstr.toString();
798: }
799:
800: // International Support
801:
802: /**
803: * International version of getString(). Overrides getNationalString
804: * in DataType for date, time, and timestamp.
805: *
806: * @exception StandardException Thrown on error
807: */
808: protected String getNationalString(LocaleFinder localeFinder)
809: throws StandardException {
810: if (isNull()) {
811: return getString();
812: }
813:
814: return localeFinder.getDateFormat().format(
815: getDate(new GregorianCalendar()));
816: }
817:
818: /**
819: This helper routine tests the nullability of various parameters
820: and sets up the result appropriately.
821:
822: If source is null, a new NumberDataValue is built.
823:
824: @exception StandardException Thrown on error
825: */
826: static NumberDataValue setSource(int value, NumberDataValue source)
827: throws StandardException {
828: /*
829: ** NOTE: Most extract operations return int, so the generation of
830: ** a SQLInteger is here. Those extract operations that return
831: ** something other than int must allocate the source NumberDataValue
832: ** themselves, so that we do not allocate a SQLInteger here.
833: */
834: if (source == null)
835: source = new SQLInteger();
836:
837: source.setValue(value);
838:
839: return source;
840: }
841:
842: /**
843: * Compute the encoded date given a date
844: *
845: */
846: private static int computeEncodedDate(java.util.Date value)
847: throws StandardException {
848: return computeEncodedDate(value, null);
849: }
850:
851: static int computeEncodedDate(java.util.Date value,
852: Calendar currentCal) throws StandardException {
853: if (value == null)
854: return 0; //encoded dates have a 0 value for null
855: if (currentCal == null)
856: currentCal = new GregorianCalendar();
857: currentCal.setTime(value);
858: return SQLDate.computeEncodedDate(currentCal);
859: }
860:
861: /**
862: * Implement the date SQL function: construct a SQL date from a string, number, or timestamp.
863: *
864: * @param operand Must be a date or a string convertible to a date.
865: * @param dvf the DataValueFactory
866: *
867: * @exception StandardException standard error policy
868: */
869: public static DateTimeDataValue computeDateFunction(
870: DataValueDescriptor operand, DataValueFactory dvf)
871: throws StandardException {
872: try {
873: if (operand.isNull())
874: return new SQLDate();
875: if (operand instanceof SQLDate)
876: return (SQLDate) operand.getClone();
877:
878: if (operand instanceof SQLTimestamp) {
879: DateTimeDataValue retVal = new SQLDate();
880: retVal.setValue(operand);
881: return retVal;
882: }
883: if (operand instanceof NumberDataValue) {
884: int daysSinceEpoch = operand.getInt();
885: if (daysSinceEpoch <= 0 || daysSinceEpoch > 3652059)
886: throw StandardException.newException(
887: SQLState.LANG_INVALID_FUNCTION_ARGUMENT,
888: operand.getString(), "date");
889: Calendar cal = new GregorianCalendar(1970, 0, 1, 12, 0,
890: 0);
891: cal.add(Calendar.DATE, daysSinceEpoch - 1);
892: return new SQLDate(computeEncodedDate(cal
893: .get(Calendar.YEAR),
894: cal.get(Calendar.MONTH) + 1, cal
895: .get(Calendar.DATE)));
896: }
897: String str = operand.getString();
898: if (str.length() == 7) {
899: // yyyyddd where ddd is the day of the year
900: int year = SQLTimestamp.parseDateTimeInteger(str, 0, 4);
901: int dayOfYear = SQLTimestamp.parseDateTimeInteger(str,
902: 4, 3);
903: if (dayOfYear < 1 || dayOfYear > 366)
904: throw StandardException.newException(
905: SQLState.LANG_INVALID_FUNCTION_ARGUMENT,
906: operand.getString(), "date");
907: Calendar cal = new GregorianCalendar(year, 0, 1, 2, 0,
908: 0);
909: cal.add(Calendar.DAY_OF_YEAR, dayOfYear - 1);
910: int y = cal.get(Calendar.YEAR);
911: if (y != year)
912: throw StandardException.newException(
913: SQLState.LANG_INVALID_FUNCTION_ARGUMENT,
914: operand.getString(), "date");
915: return new SQLDate(computeEncodedDate(year, cal
916: .get(Calendar.MONTH) + 1, cal
917: .get(Calendar.DATE)));
918: }
919: // Else use the standard cast.
920: return dvf.getDateValue(str, false);
921: } catch (StandardException se) {
922: if (SQLState.LANG_DATE_SYNTAX_EXCEPTION.startsWith(se
923: .getSQLState()))
924: throw StandardException.newException(
925: SQLState.LANG_INVALID_FUNCTION_ARGUMENT,
926: operand.getString(), "date");
927: throw se;
928: }
929: } // end of computeDateFunction
930:
931: /** Adding this method to ensure that super class' setInto method doesn't get called
932: * that leads to the violation of JDBC spec( untyped nulls ) when batching is turned on.
933: */
934: public void setInto(PreparedStatement ps, int position)
935: throws SQLException, StandardException {
936:
937: ps.setDate(position, getDate((Calendar) null));
938: }
939:
940: /**
941: * Add a number of intervals to a datetime value. Implements the JDBC escape TIMESTAMPADD function.
942: *
943: * @param intervalType One of FRAC_SECOND_INTERVAL, SECOND_INTERVAL, MINUTE_INTERVAL, HOUR_INTERVAL,
944: * DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, or YEAR_INTERVAL
945: * @param intervalCount The number of intervals to add
946: * @param currentDate Used to convert time to timestamp
947: * @param resultHolder If non-null a DateTimeDataValue that can be used to hold the result. If null then
948: * generate a new holder
949: *
950: * @return startTime + intervalCount intervals, as a timestamp
951: *
952: * @exception StandardException
953: */
954: public DateTimeDataValue timestampAdd(int intervalType,
955: NumberDataValue intervalCount, java.sql.Date currentDate,
956: DateTimeDataValue resultHolder) throws StandardException {
957: return toTimestamp().timestampAdd(intervalType, intervalCount,
958: currentDate, resultHolder);
959: }
960:
961: private SQLTimestamp toTimestamp() throws StandardException {
962: return new SQLTimestamp(getEncodedDate(), 0, 0);
963: }
964:
965: /**
966: * Finds the difference between two datetime values as a number of intervals. Implements the JDBC
967: * TIMESTAMPDIFF escape function.
968: *
969: * @param intervalType One of FRAC_SECOND_INTERVAL, SECOND_INTERVAL, MINUTE_INTERVAL, HOUR_INTERVAL,
970: * DAY_INTERVAL, WEEK_INTERVAL, MONTH_INTERVAL, QUARTER_INTERVAL, or YEAR_INTERVAL
971: * @param time1
972: * @param currentDate Used to convert time to timestamp
973: * @param resultHolder If non-null a NumberDataValue that can be used to hold the result. If null then
974: * generate a new holder
975: *
976: * @return the number of intervals by which this datetime is greater than time1
977: *
978: * @exception StandardException
979: */
980: public NumberDataValue timestampDiff(int intervalType,
981: DateTimeDataValue time1, java.sql.Date currentDate,
982: NumberDataValue resultHolder) throws StandardException {
983: return toTimestamp().timestampDiff(intervalType, time1,
984: currentDate, resultHolder);
985: }
986: }
|