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 "CommonTS.java".  Description:
010     *
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.GregorianCalendar;
031    
032    import ca.uhn.hl7v2.model.DataTypeException;
033    import ca.uhn.hl7v2.model.DataTypeUtil;
034    import ca.uhn.log.HapiLog;
035    import ca.uhn.log.HapiLogFactory;
036    
037    /**
038     *
039     * This class contains functionality used by the TS class
040     * in the version 2.3.0, 2.3.1, and 2.4 packages
041     *
042     * Note: The class description below has been excerpted from the Hl7 2.4 documentation. Sectional
043     * references made below also refer to the same documentation.
044     *
045     * Format: YYYY[MM[DD[HHMM[SS[.S[S[S[S]]]]]]]][+/-ZZZZ]^<degree of precision>
046     * Contains the exact time of an event, including the date and time. The date portion of a time stamp follows the rules of a
047     * date field and the time portion follows the rules of a time field. The time zone (+/-ZZZZ) is represented as +/-HHMM
048     * offset from UTC (formerly Greenwich Mean Time (GMT)), where +0000 or -0000 both represent UTC (without offset).
049     * The specific data representations used in the HL7 encoding rules are compatible with ISO 8824-1987(E).
050     * In prior versions of HL7, an optional second component indicates the degree of precision of the time stamp (Y = year, L
051     * = month, D = day, H = hour, M = minute, S = second). This optional second component is retained only for purposes of
052     * backward compatibility.
053     * By site-specific agreement, YYYYMMDD[HHMM[SS[.S[S[S[S]]]]]][+/-ZZZZ]^<degree of precision> may be used
054     * where backward compatibility must be maintained.
055     * In the current and future versions of HL7, the precision is indicated by limiting the number of digits used, unless the
056     * optional second component is present. Thus, YYYY is used to specify a precision of "year," YYYYMM specifies a
057     * precision of "month," YYYYMMDD specifies a precision of "day," YYYYMMDDHH is used to specify a precision of
058     * "hour," YYYYMMDDHHMM is used to specify a precision of "minute," YYYYMMDDHHMMSS is used to specify a
059     * precision of seconds, and YYYYMMDDHHMMSS.SSSS is used to specify a precision of ten thousandths of a second.
060     * In each of these cases, the time zone is an optional component. Note that if the time zone is not included, the timezone
061     * defaults to that of the local time zone of the sender. Also note that a TS valued field with the HHMM part set to "0000"
062     * represents midnight of the night extending from the previous day to the day given by the YYYYMMDD part (see example
063     * below). Maximum length of the time stamp is 26. Examples:
064     * |19760704010159-0500|
065     * 1:01:59 on July 4, 1976 in the Eastern Standard Time zone (USA).
066     * |19760704010159-0400|
067     * 1:01:59 on July 4, 1976 in the Eastern Daylight Saving Time zone (USA).
068     * |198807050000|
069     * Midnight of the night extending from July 4 to July 5, 1988 in the local time zone of the sender.
070     * |19880705|
071     * Same as prior example, but precision extends only to the day. Could be used for a birthdate, if the time of birth is
072     * unknown.
073     * |19981004010159+0100|
074     * 1:01:59 on October 4, 1998 in Amsterdam, NL. (Time zone=+0100).
075     * The HL7 Standard strongly recommends that all systems routinely send the time zone offset but does not require it. All
076     * HL7 systems are required to accept the time zone offset, but its implementation is application specific. For many
077     * applications the time of interest is the local time of the sender. For example, an application in the Eastern Standard Time
078     * zone receiving notification of an admission that takes place at 11:00 PM in San Francisco on December 11 would prefer
079     * to treat the admission as having occurred on December 11 rather than advancing the date to December 12.
080     * Note: The time zone [+/-ZZZZ], when used, is restricted to legally-defined time zones and is represented in HHMM
081     * format.
082     * One exception to this rule would be a clinical system that processed patient data collected in a clinic and a nearby hospital
083     * that happens to be in a different time zone. Such applications may choose to convert the data to a common
084     * representation. Similar concerns apply to the transitions to and from daylight saving time. HL7 supports such requirements
085     * by requiring that the time zone information be present when the information is sent. It does not, however, specify which of
086     * the treatments discussed here will be applied by the receiving system.
087     * @author Neal Acharya
088     */
089    public class CommonTS {
090    
091        private static final HapiLog log = HapiLogFactory.getHapiLog(CommonTS.class);
092    
093        private CommonDT dt;
094        private CommonTM tm;
095    
096        /** Creates new ValidTS
097         * zero argument constructor.
098         * Creates an uninitailized TS datatype
099         */
100        public CommonTS() {
101        } //zero arg constructor
102    
103        /**
104         * Constructs a TS object with the given value.
105         * The stored value will be in the following
106         * format YYYY[MM[DD[HHMM[SS[.S[S[S[S]]]]]]]][+/-ZZZZ]
107         */
108        public CommonTS(String val) throws DataTypeException {
109            this.setValue(val);
110        } //end constructor
111    
112        /**
113         * This method takes in a string HL7 Time Stamp value and performs validations.
114         * The stored value will be in the following
115         * format YYYY[MM[DD[HHMM[SS[.S[S[S[S]]]]]]]][+/-ZZZZ].
116         * Note: Trailing zeros supplied in the time value (HHMM[SS[.S[S[S[S]]]]]])
117         * and GMT offset ([+/-ZZZZ]) will be preserved.
118         * Note: If the GMT offset is not supplied then the local
119         * time zone (using standard time zone format which is not modified for daylight savings)
120         * will be stored as a default.
121         */
122        public void setValue(String val) throws DataTypeException {
123            if (val != null && !val.equals("") && !val.equals("\"\"")) {
124                try {
125                    //check the length of the input value, ensure that it is no less than
126                    //8 characters in length
127                    if (val.length() < 4) {
128                        String msg = "The length of the TS datatype value must be at least 4 characters in length.";
129                        DataTypeException e = new DataTypeException(msg);
130                        throw e;
131                    }
132    
133                    //check the length of the input value, ensure that it is not greater
134                    //than 24 characters in length
135                    if (val.length() > 24) {
136                        String msg = "The length of the TS datatype value must not be more than 24 characters in length.";
137                        DataTypeException e = new DataTypeException(msg);
138                        throw e;
139                    }
140    
141                    //at this point we know that we have a value that should conform to the DT
142                    //datatype and possibly a value that should conform to the TM datatype
143                    String dateVal = null;
144                    String timeVal = null;
145                    String timeValLessOffset = null;
146                    int sp = val.indexOf("+");
147                    int sm = val.indexOf("-");
148                    int indexOfSign = -1;
149                    boolean offsetExists = false;
150                    boolean timeValIsOffsetOnly = false;
151                    if ((sp != -1) || (sm != -1)) {
152                        offsetExists = true;
153                    }
154                    if (sp != -1)
155                        indexOfSign = sp;
156                    if (sm != -1)
157                        indexOfSign = sm;
158    
159                    if (offsetExists == false) {
160                        if (val.length() <= 8) {
161                            dateVal = val;
162                        }
163                        else {
164                            //here we know that a time value is present
165                            dateVal = val.substring(0, 8);
166                            timeVal = val.substring(8);
167                            timeValLessOffset = timeVal;
168                        }
169                    } //offset not exist
170    
171                    if (offsetExists == true) {
172                        if (indexOfSign > 8) {
173                            dateVal = val.substring(0, 8);
174                            timeVal = val.substring(8);
175                            timeValLessOffset = val.substring(8, indexOfSign);
176                        }
177                        else {
178                            //we know that the time val is simply the offset
179                            dateVal = val.substring(0, indexOfSign);
180                            timeVal = val.substring(indexOfSign);
181                            timeValIsOffsetOnly = true;
182                        }
183                    } //offset exists
184    
185                    //create date object
186                    dt = new CommonDT();
187                    //set the value of the date object to the input date value
188                    dt.setValue(dateVal);
189                    //if the offset does not exist and a timvalue does not exist then
190                    //we must provide a default offset = to the local time zone
191                    if (timeVal == null && offsetExists == false) {
192                        int defaultOffset = DataTypeUtil.getLocalGMTOffset();
193                        tm = new CommonTM();
194                        tm.setOffset(defaultOffset);
195                    } //end if
196    
197                    //if we have a time value then make a new time object and set it to the
198                    //input time value (as long as the time val has time + offset or just time only)
199                    if (timeVal != null && timeValIsOffsetOnly == false) {
200                        //must make sure that the time component contains both hours and minutes
201                        //at the very least -- must be at least 4chars in length.
202                        if (timeValLessOffset.length() < 4) {
203                            String msg =
204                                "The length of the time component for the TM datatype"
205                                    + " value does not conform to the allowable format"
206                                    + " YYYY[MM[DD[HHMM[SS[.S[S[S[S]]]]]]]][+/-ZZZZ].";
207                            DataTypeException e = new DataTypeException(msg);
208                            throw e;
209                        } //end if
210                        tm = new CommonTM();
211                        tm.setValue(timeVal);
212                    } //end if
213    
214                    //if we have a time value and it only has the offset then make a new
215                    //time object and set the offset value to the input value
216                    if (timeVal != null && timeValIsOffsetOnly == true) {
217                        //we know that the time value is just the offset so we
218                        //must check to see if it is the right length before setting the
219                        //offset field in the tm object
220                        if (timeVal.length() != 5) {
221                            String msg =
222                                "The length of the GMT offset for the TM datatype value does"
223                                    + " not conform to the allowable format [+/-ZZZZ]";
224                            DataTypeException e = new DataTypeException(msg);
225                            throw e;
226                        } //end if 
227                        tm = new CommonTM();
228                        //first extract the + sign from the offset value string if it exists
229                        if (timeVal.indexOf("+") == 0) {
230                            timeVal = timeVal.substring(1);
231                        } //end if
232                        int signedOffset = Integer.parseInt(timeVal);
233                        tm.setOffset(signedOffset);
234                    } //end if
235                } //end try
236    
237                catch (DataTypeException e) {
238                    throw e;
239                } //end catch
240    
241                catch (Exception e) {
242                    throw new DataTypeException(e);
243                } //end catch
244            } //end if
245            else {
246                //set the private value field to null or empty space.
247                if (val == null) {
248                    dt = null;
249                    tm = null;
250                } //end if
251                if (val != null && val.equals("")) {
252                    dt = new CommonDT();
253                    dt.setValue("");
254                    tm = new CommonTM();
255                    tm.setValue("");
256                } //end if
257                if (val != null && val.equals("\"\"")) {
258                    dt = new CommonDT();
259                    dt.setValue("\"\"");
260                    tm = new CommonTM();
261                    tm.setValue("\"\"");
262                } //end if
263            } //end else    
264    
265        } // end method
266    
267        /**
268         * This method takes in integer values for the year and month and day
269         * and performs validations, it then sets the value in the object
270         * formatted as an HL7 Time Stamp value with year&month&day precision (YYYYMMDD).
271         *
272         */
273        public void setDatePrecision(int yr, int mnth, int dy) throws DataTypeException {
274            try {
275                //create date object if there isn't one
276                if (dt == null) {
277                    dt = new CommonDT();
278                }
279                //set the value of the date object to the input date value
280                dt.setYearMonthDayPrecision(yr, mnth, dy);
281                //clear the time value object
282                tm = null;
283            } //end try
284    
285            catch (DataTypeException e) {
286                throw e;
287            } //end catch
288    
289            catch (Exception e) {
290                throw new DataTypeException(e);
291            } //end catch
292        } //end method
293    
294        /**
295         * This method takes in integer values for the year, month, day, hour
296         * and minute and performs validations, it then sets the value in the object
297         * formatted as an HL7 Time Stamp value with year&month&day&hour&minute precision (YYYYMMDDHHMM).
298         */
299        public void setDateMinutePrecision(int yr, int mnth, int dy, int hr, int min) throws DataTypeException {
300            try {
301                //set the value of the date object to the input date value
302                this.setDatePrecision(yr, mnth, dy);
303                //create new time object is there isn't one
304                if (tm == null) {
305                    tm = new CommonTM();
306                }
307                //set the value of the time object to the minute precision with the input values
308                tm.setHourMinutePrecision(hr, min);
309            } //end try
310    
311            catch (DataTypeException e) {
312                throw e;
313            } //end catch
314    
315            catch (Exception e) {
316                throw new DataTypeException(e);
317            } //end catch
318        } //end method
319    
320        /**
321         * This method takes in integer values for the year, month, day, hour, minute, seconds,
322         * and fractional seconds (going to the tenthousandths precision).
323         * The method performs validations and then sets the value in the object formatted as an
324         * HL7 time value with a precision that starts from the year and goes down to the tenthousandths
325         * of a second (YYYYMMDDHHMMSS.SSSS).
326         * The Gmt Offset will not be effected.
327         * Note: all of the precisions from tenths down to
328         * tenthousandths of a second are optional. If the precision goes below tenthousandths
329         * of a second then the second value will be rounded to the nearest tenthousandths of a second.
330         */
331        public void setDateSecondPrecision(int yr, int mnth, int dy, int hr, int min, float sec) throws DataTypeException {
332            try {
333                //set the value of the date object to the input date value
334                this.setDatePrecision(yr, mnth, dy);
335                //create new time object is there isn't one
336                if (tm == null) {
337                    tm = new CommonTM();
338                }
339                //set the value of the time object to the second precision with the input values
340                tm.setHourMinSecondPrecision(hr, min, sec);
341            } //end try
342    
343            catch (DataTypeException e) {
344                throw e;
345            } //end catch
346    
347            catch (Exception e) {
348                throw new DataTypeException(e);
349            } //end catch
350        } //end method
351    
352        /**
353         * This method takes in the four digit (signed) GMT offset and sets the offset
354         * field
355         */
356        public void setOffset(int signedOffset) throws DataTypeException {
357            try {
358                //create new time object is there isn't one
359                if (tm == null) {
360                    tm = new CommonTM();
361                }
362                //set the offset value of the time object to the input value
363                tm.setOffset(signedOffset);
364            }
365    
366            catch (DataTypeException e) {
367                throw e;
368            } //end catch
369    
370            catch (Exception e) {
371                throw new DataTypeException(e);
372            } //end catch
373        } //end method
374    
375        /**
376         * Returns the HL7 TS string value.
377         */
378        public String getValue() {
379            String value = null;
380            if (dt != null) {
381                value = dt.getValue();
382            } //end if
383            if (tm != null && value != null && !value.equals("")) {
384                if (tm.getValue() != null && !tm.getValue().equals("")) {
385                    //here we know we have a delete value or separate date and the time values supplied
386                    if (tm.getValue().equals("\"\"") && dt.getValue().equals("\"\"")) {
387                        //set value to the delete value ("")
388                        value = "\"\"";
389                    }
390                    else{
391                        //set value to date concatonated with time value
392                        value = value + tm.getValue();
393                    }                
394                } //end if
395                if (tm.getValue() == null || tm.getValue().equals("")) {
396                    //here we know we both have the date and just the time offset value
397                    //change the offset value from an integer to a signed string
398                    int offset = tm.getGMTOffset();
399                    String offsetStr = "";
400                    if (offset > -99) {
401                        offsetStr = DataTypeUtil.preAppendZeroes(Math.abs(offset), 4);
402                        if (tm.getGMTOffset() >= 0) {
403                            offsetStr = "+" + offsetStr;
404                        } //end if
405                        else {
406                            offsetStr = "-" + offsetStr;
407                        } //end else
408                    }
409                    value = value + offsetStr;
410                } //end if
411            } //end if
412            return value;
413        } //end method
414    
415        /**
416         * Returns the year as an integer.
417         */
418        public int getYear() {
419            int year = 0;
420            if (dt != null) {
421                year = dt.getYear();
422            } //end if
423            return year;
424        } //end method
425    
426        /**
427         * Returns the month as an integer.
428         */
429        public int getMonth() {
430            int month = 0;
431            if (dt != null) {
432                month = dt.getMonth();
433            } //end if
434            return month;
435        } //end method
436    
437        /**
438         * Returns the day as an integer.
439         */
440        public int getDay() {
441            int day = 0;
442            if (dt != null) {
443                day = dt.getDay();
444            } //end if
445            return day;
446        } //end method
447    
448        /**
449         * Returns the hour as an integer.
450         */
451        public int getHour() {
452            int hour = 0;
453            if (tm != null) {
454                hour = tm.getHour();
455            } //end if
456            return hour;
457        } //end method
458    
459        /**
460         * Returns the minute as an integer.
461         */
462        public int getMinute() {
463            int minute = 0;
464            if (tm != null) {
465                minute = tm.getMinute();
466            } //end if
467            return minute;
468        } //end method
469    
470        /**
471         * Returns the second as an integer.
472         */
473        public int getSecond() {
474            int seconds = 0;
475            if (tm != null) {
476                seconds = tm.getSecond();
477            } //end if
478            return seconds;
479        } //end method
480    
481        /**
482         * Returns the fractional second value as a float.
483         */
484        public float getFractSecond() {
485            float fractionOfSec = 0;
486            if (tm != null) {
487                fractionOfSec = tm.getFractSecond();
488            } //end if
489            return fractionOfSec;
490        } //end method
491    
492        /**
493         * Returns the GMT offset value as an integer.
494         */
495        public int getGMTOffset() {
496            int offSet = 0;
497            if (tm != null) {
498                offSet = tm.getGMTOffset();
499            } //end if
500            return offSet;
501        } //end method
502    
503        /**
504         * Returns a string value representing the input Gregorian Calendar object in
505         * an Hl7 TimeStamp Format.
506         */
507        public static String toHl7TSFormat(GregorianCalendar cal) throws DataTypeException {
508            String val = "";
509            try {
510                //set the input cal object so that it can report errors
511                //on it's value
512                cal.setLenient(false);
513                int calYear = cal.get(GregorianCalendar.YEAR);
514                int calMonth = cal.get(GregorianCalendar.MONTH) + 1;
515                int calDay = cal.get(GregorianCalendar.DAY_OF_MONTH);
516                int calHour = cal.get(GregorianCalendar.HOUR_OF_DAY);
517                int calMin = cal.get(GregorianCalendar.MINUTE);
518                int calSec = cal.get(GregorianCalendar.SECOND);
519                int calMilli = cal.get(GregorianCalendar.MILLISECOND);
520                //the inputs seconds and milli seconds should be combined into a float type
521                float fractSec = calMilli / 1000F;
522                float calSecFloat = calSec + fractSec;
523                int calOffset = cal.get(GregorianCalendar.ZONE_OFFSET);
524                //Note the input's Offset value is in milliseconds, we must convert it to
525                //a 4 digit integer in the HL7 Offset format.
526                int offSetSignInt;
527                if (calOffset < 0) {
528                    offSetSignInt = -1;
529                }
530                else {
531                    offSetSignInt = 1;
532                }
533                //get the absolute value of the gmtOffSet
534                int absGmtOffSet = Math.abs(calOffset);
535                int gmtOffSetHours = absGmtOffSet / (3600 * 1000);
536                int gmtOffSetMin = (absGmtOffSet / 60000) % (60);
537                //reset calOffset
538                calOffset = ((gmtOffSetHours * 100) + gmtOffSetMin) * offSetSignInt;
539                //Create an object of the TS class and populate it with the above values
540                //then return the HL7 string value from the object
541                CommonTS ts = new CommonTS();
542                ts.setDateSecondPrecision(calYear, calMonth, calDay, calHour, calMin, calSecFloat);
543                ts.setOffset(calOffset);
544                val = ts.getValue();
545            } // end try
546    
547            catch (DataTypeException e) {
548                throw e;
549            } //end catch
550    
551            catch (Exception e) {
552                throw new DataTypeException(e);
553            } //end catch
554            return val;
555        } //end method
556    
557    } //end class