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    
028    package ca.uhn.hl7v2.model.primitive;
029    
030    import java.util.Calendar;
031    import java.util.Date;
032    import java.util.GregorianCalendar;
033    import java.util.TimeZone;
034    import java.io.Serializable;
035    
036    import ca.uhn.hl7v2.model.DataTypeException;
037    import 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")
077    public 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 theCalendar The calendar 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 theCalendar The calendar 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            Calendar retVal = Calendar.getInstance();
634            retVal.set(Calendar.HOUR_OF_DAY, getHour());
635            retVal.set(Calendar.MINUTE, getMinute());
636            retVal.set(Calendar.SECOND, getSecond());
637            
638            float fractSecond = getFractSecond();
639            retVal.set(Calendar.MILLISECOND, (int) (fractSecond * 1000.0));
640            
641            int gmtOff = getGMTOffset();
642            if (gmtOff != GMT_OFFSET_NOT_SET_VALUE && !omitOffsetFg) {
643                retVal.set(Calendar.ZONE_OFFSET, (gmtOff/100) * (1000 * 60 * 60));
644                
645                /*
646                 * The following sets the TimeZone associated with the returned calendar
647                 * to use the offset specified in the value if this conflicts with the
648                 * value it already contains.
649                 * 
650                 * This is needed in situations where daylight savings is in effect
651                 * during part of the year, and a date is parsed which contains the 
652                 * other part of the year (i.e. parsing a DST DateTime when it is not actually
653                 * DST according to the system clock).
654                 * 
655                 * See CommonTSTest#testGetCalendarRespectsDaylightSavings() for an example
656                 * which fails if this is removed.
657                 */
658                if (retVal.getTimeZone().getRawOffset() != retVal.get(Calendar.ZONE_OFFSET)) {
659                        int hrOffset = gmtOff / 100;
660                        int minOffset = gmtOff % 100;
661                        StringBuilder tzBuilder = new StringBuilder("GMT");
662                        
663                        if (hrOffset < 0) {
664                            tzBuilder.append('-');
665                        }
666                        tzBuilder.append(Math.abs(hrOffset));
667                        tzBuilder.append(':');
668                        if (minOffset < 10) {
669                            tzBuilder.append('0');
670                        }
671                        tzBuilder.append(minOffset);
672                        
673                        retVal.setTimeZone(TimeZone.getTimeZone(tzBuilder.toString()));
674                }
675                
676            }
677            
678            return retVal;
679        }
680    
681        
682        /**
683         * <p>Return the value as a date object</p>
684         * 
685         * <b>Note that only the time component of the return value is set to
686         * the value from this object. Returned value will have today's date</b> 
687         * Note: Date is timezone-agnostic, representing always GMT time
688         * @since 1.1 
689         */
690        public Date getValueAsDate() {
691            return getValueAsCalendar().getTime();
692        }    
693        
694        /**
695         * Returns the hour as an integer.
696         */
697        public int getHour() {
698            return hour;
699        } //end method
700    
701        /**
702         * Returns the minute as an integer.
703         */
704        public int getMinute() {
705            return minute;
706        } //end method
707    
708        /**
709         * Returns the second as an integer.
710         */
711        public int getSecond() {
712            return second;
713        } //end method
714    
715        /**
716         * Returns the fractional second value as a float.
717         */
718        public float getFractSecond() {
719            return fractionOfSec;
720        } //end method
721    
722        /**
723         * Returns the GMT offset value as an integer, {@link #GMT_OFFSET_NOT_SET_VALUE} if not set.  
724         */
725        public int getGMTOffset() {
726            return offSet;
727        } //end method
728        
729        /**
730         * Returns a string value representing the input Gregorian Calendar object in
731         * an Hl7 Time Format.
732         */
733        public static String toHl7TMFormat(GregorianCalendar cal) throws DataTypeException {
734            String val = "";
735            try {
736                //set the input cal object so that it can report errors
737                //on it's value
738                cal.setLenient(false);
739                int calHour = cal.get(GregorianCalendar.HOUR_OF_DAY);
740                int calMin = cal.get(GregorianCalendar.MINUTE);
741                int calSec = cal.get(GregorianCalendar.SECOND);
742                int calMilli = cal.get(GregorianCalendar.MILLISECOND);
743                //the inputs seconds and milli seconds should be combined into a float type
744                float fractSec = calMilli / 1000F;
745                float calSecFloat = calSec + fractSec;
746                int calOffset = cal.get(GregorianCalendar.ZONE_OFFSET) + cal.get(GregorianCalendar.DST_OFFSET); 
747                //Note the input's Offset value is in milliseconds, we must convert it to
748                //a 4 digit integer in the HL7 Offset format.
749                int offSetSignInt;
750                if (calOffset < 0) {
751                    offSetSignInt = -1;
752                }
753                else {
754                    offSetSignInt = 1;
755                }
756                //get the absolute value of the gmtOffSet
757                int absGmtOffSet = Math.abs(calOffset);
758                int gmtOffSetHours = absGmtOffSet / (3600 * 1000);
759                int gmtOffSetMin = (absGmtOffSet / 60000) % (60);
760                //reset calOffset
761                calOffset = ((gmtOffSetHours * 100) + gmtOffSetMin) * offSetSignInt;
762                //Create an object of the TS class and populate it with the above values
763                //then return the HL7 string value from the object
764                CommonTM tm = new CommonTM();
765                tm.setHourMinSecondPrecision(calHour, calMin, calSecFloat);
766                tm.setOffset(calOffset);
767                val = tm.getValue();
768            } // end try
769    
770            catch (DataTypeException e) {
771                throw e;
772            } //end catch
773    
774            catch (Exception e) {
775                throw new DataTypeException(e);
776            } //end catch
777            return val;
778        } //end method
779    
780    } //end class