001/**
002 * The contents of this file are subject to the Mozilla Public License Version 1.1
003 * (the "License"); you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at http://www.mozilla.org/MPL/
005 * Software distributed under the License is distributed on an "AS IS" basis,
006 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the
007 * specific language governing rights and limitations under the License.
008 *
009 * The Original Code is "CommmonTM.java".  Description:
010 * "Note: The class description below has been excerpted from the Hl7 2.4 documentation"
011 *
012 * The Initial Developer of the Original Code is University Health Network. Copyright (C)
013 * 2001.  All Rights Reserved.
014 *
015 * Contributor(s): ______________________________________.
016 *
017 * Alternatively, the contents of this file may be used under the terms of the
018 * GNU General Public License (the "GPL"), in which case the provisions of the GPL are
019 * applicable instead of those above.  If you wish to allow use of your version of this
020 * file only under the terms of the GPL and not to allow others to use your version
021 * of this file under the MPL, indicate your decision by deleting  the provisions above
022 * and replace  them with the notice and other provisions required by the GPL License.
023 * If you do not delete the provisions above, a recipient may use your version of
024 * this file under either the MPL or the GPL.
025 *
026 */
027
028package ca.uhn.hl7v2.model.primitive;
029
030import java.util.Calendar;
031import java.util.Date;
032import java.util.GregorianCalendar;
033import java.util.TimeZone;
034import java.io.Serializable;
035
036import ca.uhn.hl7v2.model.DataTypeException;
037import ca.uhn.hl7v2.model.DataTypeUtil;
038
039/**
040 * This class contains functionality used by the TM class
041 * in the version 2.3.0, 2.3.1, and 2.4 packages
042 *
043 * Note: The class description below has been excerpted from the Hl7 2.4 documentation. Sectional
044 * references made below also refer to the same documentation.
045 *
046 * Format: HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]
047 * In prior versions of HL7, this data type was always specified to be in the
048 * format HHMM[SS[.SSSS]][+/-ZZZZ] using a 24 hour clock notation. In the
049 * current and future versions, the precision of a time may be expressed by
050 * limiting the number of digits used with the format specification as shown
051 * above. By site-specific agreement, HHMM[SS[.SSSS]][+/-ZZZZ] may be used where
052 * backward compatibility must be maintained.
053 * Thus, HH is used to specify a precision of "hour," HHMM is used to specify a
054 * precision of "minute," HHMMSS is used to specify a precision of seconds, and
055 * HHMMSS.SSSS is used to specify a precision of ten-thousandths of a second.
056 * In each of these cases, the time zone is an optional component. The fractional
057 * seconds could be sent by a transmitter who requires greater precision than whole
058 * seconds. Fractional representations of minutes, hours or other higher-order units
059 * of time are not permitted.
060 * Note: The time zone [+/-ZZZZ], when used, is restricted to legally-defined time zones
061 * and is represented in HHMM format.
062 * The time zone of the sender may be sent optionally as an offset from the coordinated
063 * universal time (previously known as Greenwich Mean Time). Where the time zone
064 * is not present in a particular TM field but is included as part of the date/time
065 * field in the MSH segment, the MSH value will be used as the default time zone.
066 * Otherwise, the time is understood to refer to the local time of the sender.
067 * Midnight is represented as 0000.
068 * Examples:|235959+1100| 1 second before midnight in a time zone eleven hours
069 * ahead of Universal Coordinated Time (i.e., east of Greenwich).
070 * |0800| Eight AM, local time of the sender.
071 * |093544.2312| 44.2312 seconds after Nine thirty-five AM, local time of sender.
072 * |13| 1pm (with a precision of hours), local time of sender.
073 * @author Neal Acharya
074 */
075
076@SuppressWarnings("serial")
077public class CommonTM implements Serializable {
078    
079        /**
080     * Value returned by {@link #getGMTOffset()} if no offset is set
081     */
082    public static final int GMT_OFFSET_NOT_SET_VALUE = -99;
083
084    private String value;
085    private int hour;
086    private int minute;
087    private int second;
088    private float fractionOfSec;
089    private int offSet;
090    private boolean omitOffsetFg = false;
091
092    /**
093     * Constructs a TM datatype with fields initialzed to zero and the value set to
094     * null.
095     */
096    public CommonTM() {
097        //initialize all DT fields
098        value = null;
099        hour = 0;
100        minute = 0;
101        second = 0;
102        fractionOfSec = 0;
103        offSet = GMT_OFFSET_NOT_SET_VALUE;
104    } //end constructor
105
106    /**
107     * Constructs a TM object with the given value.
108     * The stored value will be in the following
109     * format HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ].
110     */
111    public CommonTM(String val) throws DataTypeException {
112        this.setValue(val);
113    } //end constructor
114
115    /**
116     * This method takes in a string HL7 Time value and performs validations
117     * then sets the value field.  The stored value will be in the following
118     * format HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ].
119     * Note: Trailing zeros supplied in the time value (HH[MM[SS[.S[S[S[S]]]]]])
120     * and GMT offset ([+/-ZZZZ]) will be preserved.
121     * Note: If the GMT offset is not supplied then the local
122     * time zone (using standard time zone format which is not modified for daylight savings)
123     * will be stored as a default. Passing in <code>null</code> clears any existing value.
124     */
125    public void setValue(String val) throws DataTypeException {
126
127        if (val != null && !val.equals("") && !val.equals("\"\"")) {
128            //check to see if any of the following characters exist: "." or "+/-"
129            //this will help us determine the acceptable lengths
130
131            int d = val.indexOf(".");
132            int sp = val.indexOf("+");
133            int sm = val.indexOf("-");
134            int indexOfSign = -1;
135            boolean offsetExists = false;
136            if ((sp != -1) || (sm != -1))
137                offsetExists = true;
138            if (sp != -1)
139                indexOfSign = sp;
140            if (sm != -1)
141                indexOfSign = sm;
142
143            try {
144                //If the GMT offset exists then extract it from the input string and store it
145                //in another variable called tempOffset. Also, store the time value
146                //(without the offset)in a separate variable called timeVal.
147                //If there is no GMT offset then simply set timeVal to val.
148                String timeVal = val;
149                String tempOffset = null;
150                if (offsetExists) {
151                    timeVal = val.substring(0, indexOfSign);
152                    tempOffset = val.substring(indexOfSign);
153                } //end if
154
155                if (offsetExists && (tempOffset.length() != 5)) {
156                    //The length of the GMT offset must be 5 characters (including the sign)
157                    String msg =
158                        "The length of the TM datatype value does not conform to an allowable"
159                            + " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
160                    DataTypeException e = new DataTypeException(msg);
161                    throw e;
162                } //end if
163
164                if (d != -1) {
165                    //here we know that decimal exists
166                    //thus length of the time value can be between 8 and 11 characters
167                    if ((timeVal.length() < 8) || (timeVal.length() > 11)) {
168                        String msg =
169                            "The length of the TM datatype value does not conform to an allowable"
170                                + " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
171                        DataTypeException e = new DataTypeException(msg);
172                        throw e;
173                    } //end if
174                } //end if
175
176                if (d == -1) {
177                    //here we know that the decimal does not exist
178                    //thus length of the time value can be 2 or 4 or 6 characters
179                    if ((timeVal.length() != 2) && (timeVal.length() != 4) && (timeVal.length() != 6)) {
180                        String msg =
181                            "The length of the TM datatype value does not conform to an allowable"
182                                + " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]";
183                        DataTypeException e = new DataTypeException(msg);
184                        throw e;
185                    } //end if
186                } //end if
187
188                //We will now try to validate the timeVal portion of the TM datatype value
189                if (timeVal.length() >= 2) {
190                    //extract the hour data from the input value.  If the first 2 characters
191                    //are not numeric then a number format exception will be generated
192                    int hrInt = Integer.parseInt(timeVal.substring(0, 2));
193                    //check to see if the hour value is valid
194                    if ((hrInt < 0) || (hrInt > 23)) {
195                        String msg = "The hour value of the TM datatype must be >=0 and <=23";
196                        DataTypeException e = new DataTypeException(msg);
197                        throw e;
198                    } //end if
199                    hour = hrInt;
200                } //end if
201
202                if (timeVal.length() >= 4) {
203                    //extract the minute data from the input value
204                    //If these characters are not numeric then a number
205                    //format exception will be generated
206                    int minInt = Integer.parseInt(timeVal.substring(2, 4));
207                    //check to see if the minute value is valid
208                    if ((minInt < 0) || (minInt > 59)) {
209                        String msg = "The minute value of the TM datatype must be >=0 and <=59";
210                        DataTypeException e = new DataTypeException(msg);
211                        throw e;
212                    } //end if
213                    minute = minInt;
214                } //end if
215
216                if (timeVal.length() >= 6) {
217                    //extract the seconds data from the input value
218                    //If these characters are not numeric then a number
219                    //format exception will be generated
220                    int secInt = Integer.parseInt(timeVal.substring(4, 6));
221                    //check to see if the seconds value is valid
222                    if ((secInt < 0) || (secInt > 59)) {
223                        String msg = "The seconds value of the TM datatype must be >=0 and <=59";
224                        DataTypeException e = new DataTypeException(msg);
225                        throw e;
226                    } //end if
227                    second = secInt;
228                } //end if
229
230                if (timeVal.length() >= 8) {
231                    //extract the fractional second value from the input value
232                    //If these characters are not numeric then a number
233                    //format exception will be generated
234                    float fract = Float.parseFloat(timeVal.substring(6));
235                    //check to see if the fractional second value is valid
236                    if ((fract < 0) || (fract >= 1)) {
237                        String msg = "The fractional second value of the TM datatype must be >= 0 and < 1";
238                        DataTypeException e = new DataTypeException(msg);
239                        throw e;
240                    } //end if
241                    fractionOfSec = fract;
242                } //end if
243
244                //We will now try to validate the tempOffset portion of the TM datatype value
245                if (offsetExists) {
246                    //in case the offset are a series of zeros we should not omit displaying
247                    //it in the return value from the getValue() method
248                    omitOffsetFg = false;
249                    //remove the sign from the temp offset
250                    String tempOffsetNoS = tempOffset.substring(1);
251                    //extract the hour data from the offset value.  If the first 2 characters
252                    //are not numeric then a number format exception will be generated
253                    int offsetInt = Integer.parseInt(tempOffsetNoS.substring(0, 2));
254                    //check to see if the hour value is valid
255                    if ((offsetInt < 0) || (offsetInt > 23)) {
256                        String msg = "The GMT offset hour value of the TM datatype must be >=0 and <=23";
257                        DataTypeException e = new DataTypeException(msg);
258                        throw e;
259                    } //end if
260                    //extract the minute data from the offset value.  If these characters
261                    //are not numeric then a number format exception will be generated
262                    offsetInt = Integer.parseInt(tempOffsetNoS.substring(2, 4));
263                    //check to see if the minute value is valid
264                    if ((offsetInt < 0) || (offsetInt > 59)) {
265                        String msg = "The GMT offset minute value of the TM datatype must be >=0 and <=59";
266                        DataTypeException e = new DataTypeException(msg);
267                        throw e;
268                    } //end if
269                    //validation done, update the offSet field
270                    offSet = Integer.parseInt(tempOffsetNoS);
271                    //add the sign back to the offset if it is negative
272                    if (sm != -1) {
273                        offSet = -1 * offSet;
274                    } //end if
275                } //end if
276
277                //If the GMT offset has not been supplied then set the offset to the
278                //local timezone
279                //[Bryan: changing this to omit time zone because erroneous if parser in different zone than sender]
280                if (!offsetExists) {
281                    omitOffsetFg = true;
282                    // set the offSet field to the current time and local time zone
283                    //offSet = DataTypeUtil.getLocalGMTOffset();
284                } //end if
285
286                //validations are now done store the time value into the private value field
287                value = timeVal;
288            } //end try
289
290            catch (DataTypeException e) {
291                throw e;
292            } //end catch
293
294            catch (Exception e) {
295                throw new DataTypeException(e);
296            } //end catch
297        } //end if
298        else {
299            //set the private value field to null or empty space.
300            value = val;
301        } //end else
302    } //end method
303
304    /**
305     * This method takes in an integer value for the hour and performs validations,
306     * it then sets the value field formatted as an HL7 time
307     * value with hour precision (HH).
308     */
309    public void setHourPrecision(int hr) throws DataTypeException {
310        try {
311            //validate input value
312            if ((hr < 0) || (hr > 23)) {
313                String msg = "The hour value of the TM datatype must be >=0 and <=23";
314                DataTypeException e = new DataTypeException(msg);
315                throw e;
316            } //end if
317            hour = hr;
318            minute = 0;
319            second = 0;
320            fractionOfSec = 0;
321            offSet = 0;
322            //Here the offset is not defined, we should omit showing it in the
323            //return value from the getValue() method
324            omitOffsetFg = true;
325            value = DataTypeUtil.preAppendZeroes(hr, 2);
326        } //end try
327
328        catch (DataTypeException e) {
329            throw e;
330        } //end catch
331
332        catch (Exception e) {
333            throw new DataTypeException(e.getMessage());
334        } //end catch
335
336    } //end method
337
338    /**
339     * This method takes in integer values for the hour and minute and performs validations,
340     * it then sets the value field formatted as an HL7 time value
341     * with hour&minute precision (HHMM).
342     */
343    public void setHourMinutePrecision(int hr, int min) throws DataTypeException {
344        try {
345            this.setHourPrecision(hr);
346            //validate input minute value
347            if ((min < 0) || (min > 59)) {
348                String msg = "The minute value of the TM datatype must be >=0 and <=59";
349                DataTypeException e = new DataTypeException(msg);
350                throw e;
351            } //end if
352            minute = min;
353            second = 0;
354            fractionOfSec = 0;
355            offSet = 0;
356            //Here the offset is not defined, we should omit showing it in the
357            //return value from the getValue() method
358            omitOffsetFg = true;
359            value = value + DataTypeUtil.preAppendZeroes(min, 2);
360        } //end try
361
362        catch (DataTypeException e) {
363            throw e;
364        } //end catch
365
366        catch (Exception e) {
367            throw new DataTypeException(e.getMessage());
368        } //end catch
369    } //end method
370
371    /**
372     * This method takes in integer values for the hour, minute, seconds, and fractional seconds
373     * (going to the tenthousandths precision).
374     * The method performs validations and then sets the value field formatted as an
375     * HL7 time value with a precision that starts from the hour and goes down to the tenthousandths
376     * of a second (HHMMSS.SSSS).
377     * Note: all of the precisions from tenths down to tenthousandths of a
378     * second are optional. If the precision goes below tenthousandths of a second then the second
379     * value will be rounded to the nearest tenthousandths of a second.
380     */
381    public void setHourMinSecondPrecision(int hr, int min, float sec) throws DataTypeException {
382        try {
383            this.setHourMinutePrecision(hr, min);
384            //multiply the seconds input value by 10000 and round the result
385            //then divide the number by tenthousand and store it back.
386            //This will round the fractional seconds to the nearest tenthousandths
387            int secMultRound = Math.round(10000F * sec);
388            sec = secMultRound / 10000F;
389            //Now store the second and fractional component
390            second = (int) Math.floor(sec);
391                        //validate input seconds value
392                        if ((second < 0) || (second >= 60)) {
393                                String msg = "The (rounded) second value of the TM datatype must be >=0 and <60";
394                                DataTypeException e = new DataTypeException(msg);
395                                throw e;
396                        } //end if
397            int fractionOfSecInt = (int) (secMultRound - (second * 10000));
398            fractionOfSec = fractionOfSecInt / 10000F;
399            String fractString = "";
400            //Now convert the fractionOfSec field to a string without the leading zero
401            if (fractionOfSec != 0.0F) {
402                fractString = (Float.toString(fractionOfSec)).substring(1);
403            } //end if
404            //Now update the value field
405            offSet = 0;
406            //Here the offset is not defined, we should omit showing it in the
407            //return value from the getValue() method
408            omitOffsetFg = true;
409            value = value + DataTypeUtil.preAppendZeroes(second, 2) + fractString;
410        } //end try
411
412        catch (DataTypeException e) {
413            throw e;
414        } //end catch
415
416        catch (Exception e) {
417            throw new DataTypeException(e);
418        } //end catch
419    } //end method
420
421    /**
422     * This method takes in the four digit (signed) GMT offset and sets the offset
423     * field
424     */
425    public void setOffset(int signedOffset) throws DataTypeException {
426        try {
427            //When this function is called an offset is being created/updated
428            //we should not omit displaying it in the return value from
429            //the getValue() method
430            omitOffsetFg = false;
431            String offsetStr = Integer.toString(signedOffset);
432            if ((signedOffset >= 0 && offsetStr.length() > 4) || (signedOffset < 0 && offsetStr.length() > 5)) {
433                //The length of the GMT offset must be no greater than 5 characters (including the sign)
434                String msg =
435                    "The length of the GMT offset for the TM datatype value does"
436                        + " not conform to the allowable format [+/-ZZZZ]. Value: " + signedOffset;
437                DataTypeException e = new DataTypeException(msg);
438                throw e;
439            } //end if
440            //obtain the absolute value of the input
441            int absOffset = Math.abs(signedOffset);
442            //extract the hour data from the offset value.
443            //first preappend zeros so we have a 4 char offset value (without sign)
444            offsetStr = DataTypeUtil.preAppendZeroes(absOffset, 4);
445            int hrOffsetInt = Integer.parseInt(offsetStr.substring(0, 2));
446            //check to see if the hour value is valid
447            if ((hrOffsetInt < 0) || (hrOffsetInt > 23)) {
448                String msg = "The GMT offset hour value of the TM datatype must be >=0 and <=23";
449                DataTypeException e = new DataTypeException(msg);
450                throw e;
451            } //end if
452            //extract the minute data from the offset value.
453            int minOffsetInt = Integer.parseInt(offsetStr.substring(2, 4));
454            //check to see if the minute value is valid
455            if ((minOffsetInt < 0) || (minOffsetInt > 59)) {
456                String msg = "The GMT offset minute value of the TM datatype must be >=0 and <=59";
457                DataTypeException e = new DataTypeException(msg);
458                throw e;
459            } //end if
460            //The input value is valid, now store it in the offset field
461            offSet = signedOffset;
462        } //end try
463
464        catch (DataTypeException e) {
465            throw e;
466        } //end catch
467
468        catch (Exception e) {
469            throw new DataTypeException(e);
470        } //end catch
471    } //end method
472
473    /**
474     * Returns the HL7 TM string value.
475     */
476    public String getValue() {
477        //combine the value field with the offSet field and return it
478        String returnVal = null;
479        if (value != null && !value.equals("")) {
480            if (omitOffsetFg == false && !value.equals("\"\"")) {
481                int absOffset = Math.abs(offSet);
482                String sign = "";
483                if (offSet >= 0) {
484                    sign = "+";
485                } //end if
486                else {
487                    sign = "-";
488                } //end else
489                returnVal = value + sign + DataTypeUtil.preAppendZeroes(absOffset, 4);
490            }
491            else {
492                returnVal = value;
493            } //end else
494        } //end if
495        return returnVal;
496    } //end method
497
498    /**
499     * Convenience setter which sets the value using a {@link Calendar} object. Passing in <code>null</code> clears any existing value.
500     * 
501     * Note: Sets fields using precision up to the minute
502     * 
503     * @param theCalendar The calendar object from which to retrieve values 
504     * @since 1.1 
505     */
506    public void setValueToMinute(Calendar theCalendar) throws DataTypeException {
507                if (theCalendar == null) {
508                        setValue((String)null);
509                        return;
510                }
511
512        int hr = theCalendar.get(Calendar.HOUR_OF_DAY);
513        int min = theCalendar.get(Calendar.MINUTE);
514        setHourMinutePrecision(hr, min);
515    }
516
517    /**
518     * Convenience setter which sets the value using a {@link Date} object. Passing in <code>null</code> clears any existing value.
519     * 
520     * Note: Sets fields using precision up to the minute
521     * Note: Date is timezone-agnostic, representing always GMT time
522     * 
523     * @param theDate The date object from which to retrieve values
524     * @since 1.1 
525     */
526    public void setValueToMinute(Date theDate) throws DataTypeException {
527                if (theDate == null) {
528                        setValue((String)null);
529                        return;
530                }
531
532                Calendar calendar = Calendar.getInstance();
533        calendar.setTime(theDate);
534        setValueToMinute(calendar);
535    }
536    
537    /**
538     * Convenience setter which sets the value using a {@link Calendar} object. Passing in <code>null</code> clears any existing value.
539     * 
540     * Note: Sets fields using precision up to the second
541     * 
542     * @param theCalendar The calendar object from which to retrieve values 
543     * @since 1.1 
544     */
545    public void setValueToSecond(Calendar theCalendar) throws DataTypeException {
546                if (theCalendar == null) {
547                        setValue((String)null);
548                        return;
549                }
550
551        int hr = theCalendar.get(Calendar.HOUR_OF_DAY);
552        int min = theCalendar.get(Calendar.MINUTE);
553        int sec = theCalendar.get(Calendar.SECOND);
554        
555        setHourMinSecondPrecision(hr, min, sec);
556    }
557
558    /**
559     * Convenience setter which sets the value using a {@link Calendar} object. Passing in <code>null</code> clears any existing value.
560     * 
561     * Note: Sets fields using precision up to the millisecond, including timezone offset
562     * 
563     * @param theCalendar The calendar object from which to retrieve values 
564     * @since 1.1 
565     */
566    public void setValue(Calendar theCalendar) throws DataTypeException {
567                if (theCalendar == null) {
568                        setValue((String)null);
569                        return;
570                }
571
572        int hr = theCalendar.get(Calendar.HOUR_OF_DAY);
573        int min = theCalendar.get(Calendar.MINUTE);
574        float sec = theCalendar.get(Calendar.SECOND) + (theCalendar.get(Calendar.MILLISECOND) / 1000.0F);
575        setHourMinSecondPrecision(hr, min, sec);
576        
577        // 3410095: care for integer overflow and timezones not at the full hour, e.g. India
578        int hourOffset= theCalendar.get(Calendar.ZONE_OFFSET) / (1000 * 60 * 60);   
579        int minuteOffset = (theCalendar.get(Calendar.ZONE_OFFSET) / (1000 * 60)) % 60;
580        int zoneOffset = hourOffset * 100 + minuteOffset;
581        setOffset(zoneOffset);
582    }
583   
584    /**
585     * Convenience setter which sets the value using a {@link Calendar} object. Passing in <code>null</code> clears any existing value.
586     * 
587     * Note: Sets fields using precision up to the millisecond, and sets the timezone offset to
588     * the current system offset
589     * Note: Date is timezone-agnostic, representing always GMT time
590     * 
591     * @param theDate The calendar object from which to retrieve values 
592     * @since 1.1 
593     */
594        public void setValue(Date theDate) throws DataTypeException {
595                if (theDate == null) {
596                        setValue((String)null);
597                        return;
598                }
599
600                GregorianCalendar cal = new GregorianCalendar();
601                cal.setTime(theDate);
602                setValue(cal);
603        }
604    
605    /**
606     * Convenience setter which sets the value using a {@link Date} object. Passing in <code>null</code> clears any existing value.
607     * 
608     * Note: Sets fields using precision up to the second
609     * Note: Date is timezone-agnostic, representing always GMT time
610     * 
611     * @param theDate The date object from which to retrieve values
612     * @since 1.1 
613     */
614    public void setValueToSecond(Date theDate) throws DataTypeException {
615                if (theDate == null) {
616                        setValue((String)null);
617                        return;
618                }
619
620        Calendar calendar = Calendar.getInstance();
621        calendar.setTime(theDate);
622        setValueToSecond(calendar);
623    }
624    
625    /**
626     * <p>Return the value as a calendar object.</p> 
627     * 
628     * <b>Note that only the time component of the return value is set to
629     * the value from this object. Returned value will have today's date</b> 
630     * @since 1.1 
631     */
632    public Calendar getValueAsCalendar() {
633        int gmtOff = getGMTOffset();
634        Calendar retVal;
635        if (gmtOff != GMT_OFFSET_NOT_SET_VALUE && !omitOffsetFg) {
636            int hrOffset = gmtOff / 100;
637            int minOffset = Math.abs(gmtOff % 100);
638            String timeZone = String.format("GMT%+d:%02d", hrOffset, minOffset);
639            retVal = new GregorianCalendar(TimeZone.getTimeZone(timeZone));
640        } else {
641            retVal = Calendar.getInstance();
642        }
643
644        retVal.set(Calendar.HOUR_OF_DAY, getHour());
645        retVal.set(Calendar.MINUTE, getMinute());
646        retVal.set(Calendar.SECOND, getSecond());
647        float fractSecond = getFractSecond();
648        retVal.set(Calendar.MILLISECOND, (int) Math.round(fractSecond * 1000.0));
649
650        return retVal;
651    }
652
653    
654    /**
655     * <p>Return the value as a date object</p>
656     * 
657     * <b>Note that only the time component of the return value is set to
658     * the value from this object. Returned value will have today's date</b> 
659     * Note: Date is timezone-agnostic, representing always GMT time
660     * @since 1.1 
661     */
662    public Date getValueAsDate() {
663        return getValueAsCalendar().getTime();
664    }    
665    
666    /**
667     * Returns the hour as an integer.
668     */
669    public int getHour() {
670        return hour;
671    } //end method
672
673    /**
674     * Returns the minute as an integer.
675     */
676    public int getMinute() {
677        return minute;
678    } //end method
679
680    /**
681     * Returns the second as an integer.
682     */
683    public int getSecond() {
684        return second;
685    } //end method
686
687    /**
688     * Returns the fractional second value as a float.
689     */
690    public float getFractSecond() {
691        return fractionOfSec;
692    } //end method
693
694    /**
695     * Returns the GMT offset value as an integer, {@link #GMT_OFFSET_NOT_SET_VALUE} if not set.  
696     */
697    public int getGMTOffset() {
698        return offSet;
699    } //end method
700    
701    /**
702     * Returns a string value representing the input Gregorian Calendar object in
703     * an Hl7 Time Format.
704     */
705    public static String toHl7TMFormat(GregorianCalendar cal) throws DataTypeException {
706        String val = "";
707        try {
708            //set the input cal object so that it can report errors
709            //on it's value
710            cal.setLenient(false);
711            int calHour = cal.get(GregorianCalendar.HOUR_OF_DAY);
712            int calMin = cal.get(GregorianCalendar.MINUTE);
713            int calSec = cal.get(GregorianCalendar.SECOND);
714            int calMilli = cal.get(GregorianCalendar.MILLISECOND);
715            //the inputs seconds and milli seconds should be combined into a float type
716            float fractSec = calMilli / 1000F;
717            float calSecFloat = calSec + fractSec;
718            int calOffset = cal.get(GregorianCalendar.ZONE_OFFSET) + cal.get(GregorianCalendar.DST_OFFSET); 
719            //Note the input's Offset value is in milliseconds, we must convert it to
720            //a 4 digit integer in the HL7 Offset format.
721            int offSetSignInt;
722            if (calOffset < 0) {
723                offSetSignInt = -1;
724            }
725            else {
726                offSetSignInt = 1;
727            }
728            //get the absolute value of the gmtOffSet
729            int absGmtOffSet = Math.abs(calOffset);
730            int gmtOffSetHours = absGmtOffSet / (3600 * 1000);
731            int gmtOffSetMin = (absGmtOffSet / 60000) % (60);
732            //reset calOffset
733            calOffset = ((gmtOffSetHours * 100) + gmtOffSetMin) * offSetSignInt;
734            //Create an object of the TS class and populate it with the above values
735            //then return the HL7 string value from the object
736            CommonTM tm = new CommonTM();
737            tm.setHourMinSecondPrecision(calHour, calMin, calSecFloat);
738            tm.setOffset(calOffset);
739            val = tm.getValue();
740        } // end try
741
742        catch (DataTypeException e) {
743            throw e;
744        } //end catch
745
746        catch (Exception e) {
747            throw new DataTypeException(e);
748        } //end catch
749        return val;
750    } //end method
751
752} //end class