/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* http://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.hl7;
import java.io.File;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.api.APIException;
import org.openmrs.api.context.Context;
import org.openmrs.util.OpenmrsConstants;
import org.openmrs.util.OpenmrsUtil;
import ca.uhn.hl7v2.HL7Exception;
/**
* HL7-related utilities
*
* @version 1.0
*/
public class HL7Util {
private static Log log = LogFactory.getLog(HL7Util.class);
// Date and time format parsers
private final static DateFormat TIMESTAMP_FORMAT = new SimpleDateFormat("yyyyMMddHHmmss.SSSZ");
private final static DateFormat TIME_FORMAT = new SimpleDateFormat("HHmmss.SSSZ");
public final static String LOCAL_TIMEZONE_OFFSET = new SimpleDateFormat("Z").format(new Date());
/**
* Converts an HL7 timestamp into a java.util.Date object. HL7 timestamps can be created with
* varying levels of precision — e.g., just the year or just the year and month, etc.
* Since java.util.Date cannot store a partial value, we fill in defaults like January, 01 at
* midnight within the current timezone.
*
* @param s HL7 timestamp to be parsed
* @return Date object
* @throws HL7Exception
* @should fail on 78
* @should handle 1978
* @should fail on 19784
* @should handle 197804
* @should fail on 197841
* @should handle 19780411
* @should fail on 197804116
* @should handle 1978041106
* @should fail on 19780411065
* @should handle 197804110615
* @should fail on 1978041106153
* @should handle 19780411061538
* @should handle 19780411061538.1
* @should handle 19780411061538.12
* @should handle 19780411061538.123
* @should handle 19780411061538.1234
* @should fail on 197804110615-5
* @should handle 197804110615-05
* @should handle 197804110615-0200
* @should not flub dst with 20091225123000
*/
public static Date parseHL7Timestamp(String s) throws HL7Exception {
// HL7 dates must at least contain year and cannot exceed 24 bytes
if (s == null || s.length() < 4 || s.length() > 24)
throw new HL7Exception("Invalid date '" + s + "'");
StringBuffer dateString = new StringBuffer();
dateString.append(s.substring(0, 4)); // year
if (s.length() >= 6)
dateString.append(s.substring(4, 6)); // month
else
dateString.append("01");
if (s.length() >= 8) {
dateString.append(s.substring(6, 8)); //day
} else
dateString.append("01");
// Parse timezone (optional in HL7 format)
String timeZoneOffset;
try {
Date parsedDay = new SimpleDateFormat("yyyyMMdd").parse(s.substring(0, 8));
timeZoneOffset = getTimeZoneOffset(s, parsedDay);
}
catch (ParseException e) {
throw new HL7Exception("Error parsing date: '" + s.substring(0, 8) + "' for time zone offset'" + s + "'", e);
}
s = s.replace(timeZoneOffset, ""); // remove the timezone from the string
if (s.length() >= 10)
dateString.append(s.substring(8, 10)); // hour
else
dateString.append("00");
if (s.length() >= 12)
dateString.append(s.substring(10, 12)); // minute
else
dateString.append("00");
if (s.length() >= 14)
dateString.append(s.substring(12, 14)); // seconds
else
dateString.append("00");
if (s.length() >= 15 && s.charAt(14) != '.') // decimal point
throw new HL7Exception("Invalid date format '" + s + "'");
else
dateString.append(".");
if (s.length() >= 16)
dateString.append(s.substring(15, 16)); // tenths
else
dateString.append("0");
if (s.length() >= 17)
dateString.append(s.substring(16, 17)); // hundredths
else
dateString.append("0");
if (s.length() >= 18)
dateString.append(s.subSequence(17, 18)); // milliseconds
else
dateString.append("0");
dateString.append(timeZoneOffset);
Date date;
try {
date = TIMESTAMP_FORMAT.parse(dateString.toString());
}
catch (ParseException e) {
throw new HL7Exception("Error parsing date '" + s + "'");
}
return date;
}
/**
* Gets the timezone string for this given fullString. If fullString contains a + or - sign, the
* strings after those are considered to be the timezone. <br/>
* <br/>
* If the fullString does not contain a timezone, the timezone is determined from the server's
* timezone on the "givenDate". (givenDate is needed to account for daylight savings time.)
*
* @param fullString the hl7 string being parsed
* @param givenDate the date that should be used if no timezone exists on the fullString
* @return a string like +0500 or -0500 for the timezone
* @should return timezone string if exists in given string
* @should return timezone for givenDate and not the current date
*/
protected static String getTimeZoneOffset(String fullString, Date givenDate) {
// Parse timezone (optional in HL7 format)
String timeZoneOffset;
int tzPlus = fullString.indexOf('+');
int tzMinus = fullString.indexOf('-');
boolean timeZoneFlag = (tzPlus > 0 || tzMinus > 0);
if (timeZoneFlag) {
int tzIndex;
if (tzPlus > 0)
tzIndex = tzPlus;
else
tzIndex = tzMinus;
timeZoneOffset = fullString.substring(tzIndex);
if (timeZoneOffset.length() != 5)
log.error("Invalid timestamp because its too short: " + timeZoneOffset);
} else {
//set default timezone offset from the current day
Calendar cal = Calendar.getInstance();
cal.setTime(givenDate);
timeZoneOffset = new SimpleDateFormat("Z").format(cal.getTime());
}
return timeZoneOffset;
}
/**
* Convenience method for parsing HL7 dates (treated just like a timestamp with only year,
* month, and day specified)
*
* @see org.openmrs.hl7.HL7Util#parseHL7Timestamp(String)
* @throws HL7Exception
*/
public static Date parseHL7Date(String s) throws HL7Exception {
return parseHL7Timestamp(s);
}
/**
* Converts an HL7 time into a java.util.Date object. Since the java.util.Date object cannot
* store just the time, the date will remain at the epoch (e.g., January 1, 1970). Time more
* precise than microseconds is ignored.
*
* @param s HL7 time to be converted
* @return Date object set to time specified by HL7
* @throws HL7Exception
* @should fail on 197804110615
* @should handle 0615
* @should handle 061538
* @should handle 061538.1
* @should handle 061538.12
* @should handle 061538.123
* @should handle 061538.1234
* @should handle 061538-0300
*/
public static Date parseHL7Time(String s) throws HL7Exception {
String timeZoneOffset = getTimeZoneOffset(s, new Date());
s = s.replace(timeZoneOffset, ""); // remove the timezone from the string
StringBuffer timeString = new StringBuffer();
if (s.length() < 2 || s.length() > 16)
throw new HL7Exception("Invalid time format '" + s + "'");
timeString.append(s.substring(0, 2)); // hour
if (s.length() >= 4)
timeString.append(s.substring(2, 4)); // minute
else
timeString.append("00");
if (s.length() >= 6)
timeString.append(s.substring(4, 6)); // seconds
else
timeString.append("00");
if (s.length() >= 7 && s.charAt(6) != '.') // decimal point
throw new HL7Exception("Invalid time format '" + s + "'");
else
timeString.append(".");
if (s.length() >= 8)
timeString.append(s.substring(7, 8)); // tenths
else
timeString.append("0");
if (s.length() >= 9)
timeString.append(s.substring(8, 9)); // hundredths
else
timeString.append("0");
if (s.length() >= 10)
timeString.append(s.subSequence(9, 10)); // milliseconds
else
timeString.append("0");
// Parse timezone (optional in HL7 format)
timeString.append(timeZoneOffset);
Date date;
try {
date = (Date) TIME_FORMAT.parse(timeString.toString());
}
catch (ParseException e) {
throw new HL7Exception("Invalid time format: '" + s + "' [" + timeString + "]", e);
}
return date;
}
/**
* Gets the destination directory for hl7 archives.
*
* @return The destination directory for the hl7 in archive
*/
public static File getHl7ArchivesDirectory() throws APIException {
return OpenmrsUtil.getDirectoryInApplicationDataDirectory(Context.getAdministrationService().getGlobalProperty(
OpenmrsConstants.GLOBAL_PROPERTY_HL7_ARCHIVE_DIRECTORY));
}
}