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