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.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 * This class contains functionality used by the TM class 039 * in the version 2.3.0, 2.3.1, and 2.4 packages 040 * 041 * Note: The class description below has been excerpted from the Hl7 2.4 documentation. Sectional 042 * references made below also refer to the same documentation. 043 * 044 * Format: HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ] 045 * In prior versions of HL7, this data type was always specified to be in the 046 * format HHMM[SS[.SSSS]][+/-ZZZZ] using a 24 hour clock notation. In the 047 * current and future versions, the precision of a time may be expressed by 048 * limiting the number of digits used with the format specification as shown 049 * above. By site-specific agreement, HHMM[SS[.SSSS]][+/-ZZZZ] may be used where 050 * backward compatibility must be maintained. 051 * Thus, HH is used to specify a precision of "hour," HHMM is used to specify a 052 * precision of "minute," HHMMSS is used to specify a precision of seconds, and 053 * HHMMSS.SSSS is used to specify a precision of ten-thousandths of a second. 054 * In each of these cases, the time zone is an optional component. The fractional 055 * seconds could be sent by a transmitter who requires greater precision than whole 056 * seconds. Fractional representations of minutes, hours or other higher-order units 057 * of time are not permitted. 058 * Note: The time zone [+/-ZZZZ], when used, is restricted to legally-defined time zones 059 * and is represented in HHMM format. 060 * The time zone of the sender may be sent optionally as an offset from the coordinated 061 * universal time (previously known as Greenwich Mean Time). Where the time zone 062 * is not present in a particular TM field but is included as part of the date/time 063 * field in the MSH segment, the MSH value will be used as the default time zone. 064 * Otherwise, the time is understood to refer to the local time of the sender. 065 * Midnight is represented as 0000. 066 * Examples:|235959+1100| 1 second before midnight in a time zone eleven hours 067 * ahead of Universal Coordinated Time (i.e., east of Greenwich). 068 * |0800| Eight AM, local time of the sender. 069 * |093544.2312| 44.2312 seconds after Nine thirty-five AM, local time of sender. 070 * |13| 1pm (with a precision of hours), local time of sender. 071 * @author Neal Acharya 072 */ 073 public class CommonTM { 074 075 private static final HapiLog log = HapiLogFactory.getHapiLog(CommonTM.class); 076 077 private String value; 078 private int hour; 079 private int minute; 080 private int second; 081 private float fractionOfSec; 082 private int offSet; 083 private char omitOffsetFg = 'n'; 084 085 /** 086 * Constructs a TM datatype with fields initialzed to zero and the value set to 087 * null. 088 */ 089 public CommonTM() { 090 //initialize all DT fields 091 value = null; 092 hour = 0; 093 minute = 0; 094 second = 0; 095 fractionOfSec = 0; 096 offSet = -99; 097 } //end constructor 098 099 /** 100 * Constructs a TM object with the given value. 101 * The stored value will be in the following 102 * format HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]. 103 */ 104 public CommonTM(String val) throws DataTypeException { 105 this.setValue(val); 106 } //end constructor 107 108 /** 109 * This method takes in a string HL7 Time value and performs validations 110 * then sets the value field. The stored value will be in the following 111 * format HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]. 112 * Note: Trailing zeros supplied in the time value (HH[MM[SS[.S[S[S[S]]]]]]) 113 * and GMT offset ([+/-ZZZZ]) will be preserved. 114 * Note: If the GMT offset is not supplied then the local 115 * time zone (using standard time zone format which is not modified for daylight savings) 116 * will be stored as a default. 117 */ 118 public void setValue(String val) throws DataTypeException { 119 120 if (val != null && !val.equals("") && !val.equals("\"\"")) { 121 //check to see if any of the following characters exist: "." or "+/-" 122 //this will help us determine the acceptable lengths 123 124 int d = val.indexOf("."); 125 int sp = val.indexOf("+"); 126 int sm = val.indexOf("-"); 127 int indexOfSign = -1; 128 boolean offsetExists = false; 129 if ((sp != -1) || (sm != -1)) 130 offsetExists = true; 131 if (sp != -1) 132 indexOfSign = sp; 133 if (sm != -1) 134 indexOfSign = sm; 135 136 try { 137 //If the GMT offset exists then extract it from the input string and store it 138 //in another variable called tempOffset. Also, store the time value 139 //(without the offset)in a separate variable called timeVal. 140 //If there is no GMT offset then simply set timeVal to val. 141 String timeVal = val; 142 String tempOffset = null; 143 if (offsetExists) { 144 timeVal = val.substring(0, indexOfSign); 145 tempOffset = val.substring(indexOfSign); 146 } //end if 147 148 if (offsetExists && (tempOffset.length() != 5)) { 149 //The length of the GMT offset must be 5 characters (including the sign) 150 String msg = 151 "The length of the TM datatype value does not conform to an allowable" 152 + " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]"; 153 DataTypeException e = new DataTypeException(msg); 154 throw e; 155 } //end if 156 157 if (d != -1) { 158 //here we know that decimal exists 159 //thus length of the time value can be between 8 and 11 characters 160 if ((timeVal.length() < 8) || (timeVal.length() > 11)) { 161 String msg = 162 "The length of the TM datatype value does not conform to an allowable" 163 + " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]"; 164 DataTypeException e = new DataTypeException(msg); 165 throw e; 166 } //end if 167 } //end if 168 169 if (d == -1) { 170 //here we know that the decimal does not exist 171 //thus length of the time value can be 2 or 4 or 6 characters 172 if ((timeVal.length() != 2) && (timeVal.length() != 4) && (timeVal.length() != 6)) { 173 String msg = 174 "The length of the TM datatype value does not conform to an allowable" 175 + " format. Format should conform to HH[MM[SS[.S[S[S[S]]]]]][+/-ZZZZ]"; 176 DataTypeException e = new DataTypeException(msg); 177 throw e; 178 } //end if 179 } //end if 180 181 //We will now try to validate the timeVal portion of the TM datatype value 182 if (timeVal.length() >= 2) { 183 //extract the hour data from the input value. If the first 2 characters 184 //are not numeric then a number format exception will be generated 185 int hrInt = Integer.parseInt(timeVal.substring(0, 2)); 186 //check to see if the hour value is valid 187 if ((hrInt < 0) || (hrInt > 23)) { 188 String msg = "The hour value of the TM datatype must be >=0 and <=23"; 189 DataTypeException e = new DataTypeException(msg); 190 throw e; 191 } //end if 192 hour = hrInt; 193 } //end if 194 195 if (timeVal.length() >= 4) { 196 //extract the minute data from the input value 197 //If these characters are not numeric then a number 198 //format exception will be generated 199 int minInt = Integer.parseInt(timeVal.substring(2, 4)); 200 //check to see if the minute value is valid 201 if ((minInt < 0) || (minInt > 59)) { 202 String msg = "The minute value of the TM datatype must be >=0 and <=59"; 203 DataTypeException e = new DataTypeException(msg); 204 throw e; 205 } //end if 206 minute = minInt; 207 } //end if 208 209 if (timeVal.length() >= 6) { 210 //extract the seconds data from the input value 211 //If these characters are not numeric then a number 212 //format exception will be generated 213 int secInt = Integer.parseInt(timeVal.substring(4, 6)); 214 //check to see if the seconds value is valid 215 if ((secInt < 0) || (secInt > 59)) { 216 String msg = "The seconds value of the TM datatype must be >=0 and <=59"; 217 DataTypeException e = new DataTypeException(msg); 218 throw e; 219 } //end if 220 second = secInt; 221 } //end if 222 223 if (timeVal.length() >= 8) { 224 //extract the fractional second value from the input value 225 //If these characters are not numeric then a number 226 //format exception will be generated 227 float fract = Float.parseFloat(timeVal.substring(6)); 228 //check to see if the fractional second value is valid 229 if ((fract < 0) || (fract >= 1)) { 230 String msg = "The fractional second value of the TM datatype must be >= 0 and < 1"; 231 DataTypeException e = new DataTypeException(msg); 232 throw e; 233 } //end if 234 fractionOfSec = fract; 235 } //end if 236 237 //We will now try to validate the tempOffset portion of the TM datatype value 238 if (offsetExists) { 239 //in case the offset are a series of zeros we should not omit displaying 240 //it in the return value from the getValue() method 241 omitOffsetFg = 'n'; 242 //remove the sign from the temp offset 243 String tempOffsetNoS = tempOffset.substring(1); 244 //extract the hour data from the offset value. If the first 2 characters 245 //are not numeric then a number format exception will be generated 246 int offsetInt = Integer.parseInt(tempOffsetNoS.substring(0, 2)); 247 //check to see if the hour value is valid 248 if ((offsetInt < 0) || (offsetInt > 23)) { 249 String msg = "The GMT offset hour value of the TM datatype must be >=0 and <=23"; 250 DataTypeException e = new DataTypeException(msg); 251 throw e; 252 } //end if 253 //extract the minute data from the offset value. If these characters 254 //are not numeric then a number format exception will be generated 255 offsetInt = Integer.parseInt(tempOffsetNoS.substring(2, 4)); 256 //check to see if the minute value is valid 257 if ((offsetInt < 0) || (offsetInt > 59)) { 258 String msg = "The GMT offset minute value of the TM datatype must be >=0 and <=59"; 259 DataTypeException e = new DataTypeException(msg); 260 throw e; 261 } //end if 262 //validation done, update the offSet field 263 offSet = Integer.parseInt(tempOffsetNoS); 264 //add the sign back to the offset if it is negative 265 if (sm != -1) { 266 offSet = -1 * offSet; 267 } //end if 268 } //end if 269 270 //If the GMT offset has not been supplied then set the offset to the 271 //local timezone 272 //[Bryan: changing this to omit time zone because erroneous if parser in different zone than sender] 273 if (!offsetExists) { 274 omitOffsetFg = 'y'; 275 // set the offSet field to the current time and local time zone 276 //offSet = DataTypeUtil.getLocalGMTOffset(); 277 } //end if 278 279 //validations are now done store the time value into the private value field 280 value = timeVal; 281 } //end try 282 283 catch (DataTypeException e) { 284 throw e; 285 } //end catch 286 287 catch (Exception e) { 288 throw new DataTypeException(e); 289 } //end catch 290 } //end if 291 else { 292 //set the private value field to null or empty space. 293 value = val; 294 } //end else 295 } //end method 296 297 /** 298 * This method takes in an integer value for the hour and performs validations, 299 * it then sets the value field formatted as an HL7 time 300 * value with hour precision (HH). 301 */ 302 public void setHourPrecision(int hr) throws DataTypeException { 303 try { 304 //validate input value 305 if ((hr < 0) || (hr > 23)) { 306 String msg = "The hour value of the TM datatype must be >=0 and <=23"; 307 DataTypeException e = new DataTypeException(msg); 308 throw e; 309 } //end if 310 hour = hr; 311 minute = 0; 312 second = 0; 313 fractionOfSec = 0; 314 offSet = 0; 315 //Here the offset is not defined, we should omit showing it in the 316 //return value from the getValue() method 317 omitOffsetFg = 'y'; 318 value = DataTypeUtil.preAppendZeroes(hr, 2); 319 } //end try 320 321 catch (DataTypeException e) { 322 throw e; 323 } //end catch 324 325 catch (Exception e) { 326 throw new DataTypeException(e.getMessage()); 327 } //end catch 328 329 } //end method 330 331 /** 332 * This method takes in integer values for the hour and minute and performs validations, 333 * it then sets the value field formatted as an HL7 time value 334 * with hour&minute precision (HHMM). 335 */ 336 public void setHourMinutePrecision(int hr, int min) throws DataTypeException { 337 try { 338 this.setHourPrecision(hr); 339 //validate input minute value 340 if ((min < 0) || (min > 59)) { 341 String msg = "The minute value of the TM datatype must be >=0 and <=59"; 342 DataTypeException e = new DataTypeException(msg); 343 throw e; 344 } //end if 345 minute = min; 346 second = 0; 347 fractionOfSec = 0; 348 offSet = 0; 349 //Here the offset is not defined, we should omit showing it in the 350 //return value from the getValue() method 351 omitOffsetFg = 'y'; 352 value = value + DataTypeUtil.preAppendZeroes(min, 2); 353 } //end try 354 355 catch (DataTypeException e) { 356 throw e; 357 } //end catch 358 359 catch (Exception e) { 360 throw new DataTypeException(e.getMessage()); 361 } //end catch 362 } //end method 363 364 /** 365 * This method takes in integer values for the hour, minute, seconds, and fractional seconds 366 * (going to the tenthousandths precision). 367 * The method performs validations and then sets the value field formatted as an 368 * HL7 time value with a precision that starts from the hour and goes down to the tenthousandths 369 * of a second (HHMMSS.SSSS). 370 * Note: all of the precisions from tenths down to tenthousandths of a 371 * second are optional. If the precision goes below tenthousandths of a second then the second 372 * value will be rounded to the nearest tenthousandths of a second. 373 */ 374 public void setHourMinSecondPrecision(int hr, int min, float sec) throws DataTypeException { 375 try { 376 this.setHourMinutePrecision(hr, min); 377 //multiply the seconds input value by 10000 and round the result 378 //then divide the number by tenthousand and store it back. 379 //This will round the fractional seconds to the nearest tenthousandths 380 int secMultRound = Math.round(10000F * sec); 381 sec = secMultRound / 10000F; 382 //Now store the second and fractional component 383 second = (int) Math.floor(sec); 384 //validate input seconds value 385 if ((second < 0) || (second >= 60)) { 386 String msg = "The (rounded) second value of the TM datatype must be >=0 and <60"; 387 DataTypeException e = new DataTypeException(msg); 388 throw e; 389 } //end if 390 int fractionOfSecInt = (int) (secMultRound - (second * 10000)); 391 fractionOfSec = fractionOfSecInt / 10000F; 392 String fractString = ""; 393 //Now convert the fractionOfSec field to a string without the leading zero 394 if (fractionOfSec != 0.0F) { 395 fractString = (Float.toString(fractionOfSec)).substring(1); 396 } //end if 397 //Now update the value field 398 offSet = 0; 399 //Here the offset is not defined, we should omit showing it in the 400 //return value from the getValue() method 401 omitOffsetFg = 'y'; 402 value = value + DataTypeUtil.preAppendZeroes(second, 2) + fractString; 403 } //end try 404 405 catch (DataTypeException e) { 406 throw e; 407 } //end catch 408 409 catch (Exception e) { 410 throw new DataTypeException(e); 411 } //end catch 412 } //end method 413 414 /** 415 * This method takes in the four digit (signed) GMT offset and sets the offset 416 * field 417 */ 418 public void setOffset(int signedOffset) throws DataTypeException { 419 try { 420 //When this function is called an offset is being created/updated 421 //we should not omit displaying it in the return value from 422 //the getValue() method 423 omitOffsetFg = 'n'; 424 String offsetStr = Integer.toString(signedOffset); 425 if ((signedOffset >= 0 && offsetStr.length() > 4) || (signedOffset < 0 && offsetStr.length() > 5)) { 426 //The length of the GMT offset must be no greater than 5 characters (including the sign) 427 String msg = 428 "The length of the GMT offset for the TM datatype value does" 429 + " not conform to the allowable format [+/-ZZZZ]. Value: " + signedOffset; 430 DataTypeException e = new DataTypeException(msg); 431 throw e; 432 } //end if 433 //obtain the absolute value of the input 434 int absOffset = Math.abs(signedOffset); 435 //extract the hour data from the offset value. 436 //first preappend zeros so we have a 4 char offset value (without sign) 437 offsetStr = DataTypeUtil.preAppendZeroes(absOffset, 4); 438 int hrOffsetInt = Integer.parseInt(offsetStr.substring(0, 2)); 439 //check to see if the hour value is valid 440 if ((hrOffsetInt < 0) || (hrOffsetInt > 23)) { 441 String msg = "The GMT offset hour value of the TM datatype must be >=0 and <=23"; 442 DataTypeException e = new DataTypeException(msg); 443 throw e; 444 } //end if 445 //extract the minute data from the offset value. 446 int minOffsetInt = Integer.parseInt(offsetStr.substring(2, 4)); 447 //check to see if the minute value is valid 448 if ((minOffsetInt < 0) || (minOffsetInt > 59)) { 449 String msg = "The GMT offset minute value of the TM datatype must be >=0 and <=59"; 450 DataTypeException e = new DataTypeException(msg); 451 throw e; 452 } //end if 453 //The input value is valid, now store it in the offset field 454 offSet = signedOffset; 455 } //end try 456 457 catch (DataTypeException e) { 458 throw e; 459 } //end catch 460 461 catch (Exception e) { 462 throw new DataTypeException(e); 463 } //end catch 464 } //end method 465 466 /** 467 * Returns the HL7 TM string value. 468 */ 469 public String getValue() { 470 //combine the value field with the offSet field and return it 471 String returnVal = null; 472 if (value != null && !value.equals("")) { 473 if (omitOffsetFg == 'n' && !value.equals("\"\"")) { 474 int absOffset = Math.abs(offSet); 475 String sign = ""; 476 if (offSet >= 0) { 477 sign = "+"; 478 } //end if 479 else { 480 sign = "-"; 481 } //end else 482 returnVal = value + sign + DataTypeUtil.preAppendZeroes(absOffset, 4); 483 } 484 else { 485 returnVal = value; 486 } //end else 487 } //end if 488 return returnVal; 489 } //end method 490 491 /** 492 * Returns the hour as an integer. 493 */ 494 public int getHour() { 495 return hour; 496 } //end method 497 498 /** 499 * Returns the minute as an integer. 500 */ 501 public int getMinute() { 502 return minute; 503 } //end method 504 505 /** 506 * Returns the second as an integer. 507 */ 508 public int getSecond() { 509 return second; 510 } //end method 511 512 /** 513 * Returns the fractional second value as a float. 514 */ 515 public float getFractSecond() { 516 return fractionOfSec; 517 } //end method 518 519 /** 520 * Returns the GMT offset value as an integer, -99 if not set. 521 */ 522 public int getGMTOffset() { 523 return offSet; 524 } //end method 525 526 /** 527 * Returns a string value representing the input Gregorian Calendar object in 528 * an Hl7 Time Format. 529 */ 530 public static String toHl7TMFormat(GregorianCalendar cal) throws DataTypeException { 531 String val = ""; 532 try { 533 //set the input cal object so that it can report errors 534 //on it's value 535 cal.setLenient(false); 536 int calHour = cal.get(GregorianCalendar.HOUR_OF_DAY); 537 int calMin = cal.get(GregorianCalendar.MINUTE); 538 int calSec = cal.get(GregorianCalendar.SECOND); 539 int calMilli = cal.get(GregorianCalendar.MILLISECOND); 540 //the inputs seconds and milli seconds should be combined into a float type 541 float fractSec = calMilli / 1000F; 542 float calSecFloat = calSec + fractSec; 543 int calOffset = cal.get(GregorianCalendar.ZONE_OFFSET) + cal.get(GregorianCalendar.DST_OFFSET); 544 //Note the input's Offset value is in milliseconds, we must convert it to 545 //a 4 digit integer in the HL7 Offset format. 546 int offSetSignInt; 547 if (calOffset < 0) { 548 offSetSignInt = -1; 549 } 550 else { 551 offSetSignInt = 1; 552 } 553 //get the absolute value of the gmtOffSet 554 int absGmtOffSet = Math.abs(calOffset); 555 int gmtOffSetHours = absGmtOffSet / (3600 * 1000); 556 int gmtOffSetMin = (absGmtOffSet / 60000) % (60); 557 //reset calOffset 558 calOffset = ((gmtOffSetHours * 100) + gmtOffSetMin) * offSetSignInt; 559 //Create an object of the TS class and populate it with the above values 560 //then return the HL7 string value from the object 561 CommonTM tm = new CommonTM(); 562 tm.setHourMinSecondPrecision(calHour, calMin, calSecFloat); 563 tm.setOffset(calOffset); 564 val = tm.getValue(); 565 } // end try 566 567 catch (DataTypeException e) { 568 throw e; 569 } //end catch 570 571 catch (Exception e) { 572 throw new DataTypeException(e); 573 } //end catch 574 return val; 575 } //end method 576 577 } //end class