View Javadoc
1   /*
2    * Copyright 2015 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.openehealth.ipf.commons.ihe.xds.core.metadata;
17  
18  import ca.uhn.hl7v2.model.DataTypeException;
19  import ca.uhn.hl7v2.model.primitive.CommonTS;
20  import lombok.Getter;
21  import lombok.Setter;
22  import org.apache.commons.lang3.StringUtils;
23  import org.joda.time.DateTime;
24  import org.joda.time.DateTimeZone;
25  import org.joda.time.format.DateTimeFormat;
26  import org.joda.time.format.DateTimeFormatter;
27  import org.openehealth.ipf.commons.ihe.xds.core.metadata.jaxbadapters.DateTimeAdapter;
28  import org.openehealth.ipf.commons.ihe.xds.core.validate.ValidationMessage;
29  import org.openehealth.ipf.commons.ihe.xds.core.validate.XDSMetaDataException;
30  
31  import javax.xml.bind.annotation.*;
32  import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
33  import java.io.Serializable;
34  import java.util.EnumMap;
35  import java.util.Map;
36  import java.util.Objects;
37  
38  /**
39   * HL7 timestamps (data type DTM) with particular precision, normalized to UTC.
40   *
41   * @author Dmytro Rud
42   */
43  @XmlAccessorType(XmlAccessType.FIELD)
44  @XmlType(name = "Timestamp", propOrder = {"dateTime", "precision"})
45  public class Timestamp implements Serializable {
46      private static final long serialVersionUID = 4324651691599629794L;
47  
48      @XmlEnum
49      @XmlType(name = "Precision", namespace = "http://www.openehealth.org/ipf/xds")
50      public enum Precision {
51          YEAR, MONTH, DAY, HOUR, MINUTE, SECOND
52      }
53  
54      private static final Map<Precision, DateTimeFormatter> FORMATTERS = new EnumMap<>(Precision.class);
55      static {
56          FORMATTERS.put(Precision.YEAR,   DateTimeFormat.forPattern("yyyy"));
57          FORMATTERS.put(Precision.MONTH,  DateTimeFormat.forPattern("yyyyMM"));
58          FORMATTERS.put(Precision.DAY,    DateTimeFormat.forPattern("yyyyMMdd"));
59          FORMATTERS.put(Precision.HOUR,   DateTimeFormat.forPattern("yyyyMMddHH"));
60          FORMATTERS.put(Precision.MINUTE, DateTimeFormat.forPattern("yyyyMMddHHmm"));
61          FORMATTERS.put(Precision.SECOND, DateTimeFormat.forPattern("yyyyMMddHHmmss"));
62      }
63  
64      /**
65       * Timestamp.
66       */
67      @XmlAttribute
68      @XmlJavaTypeAdapter(value = DateTimeAdapter.class)
69      @Getter private DateTime dateTime;
70  
71      /**
72       * Precision of the timestamp (smallest present element, e.g. YEAR for "1980").
73       */
74      @XmlAttribute
75      @Setter private Precision precision;
76  
77      public Timestamp() {
78          // only for JAXB
79      }
80  
81      /**
82       * Initializes a {@link Timestamp} object with the given datetime and precision.
83       */
84      public Timestamp(DateTime dateTime, Precision precision) {
85          setDateTime(dateTime);
86          setPrecision(precision);
87      }
88  
89      /**
90       * Creates a {@link Timestamp} object from the given string.
91       * @param s
92       *      String of the pattern <code>YYYY[MM[DD[HH[MM[SS[.S[S[S[S]]]]]]]]][+/-ZZZZ]</code>.
93       *      Milliseconds will be ignored.
94       * @return
95       *      a {@link Timestamp} object, or <code>null</code> if the parameter is <code>null</code> or empty.
96       */
97      public static Timestamp fromHL7(String s) {
98          if (StringUtils.isEmpty(s)) {
99              return null;
100         }
101 
102         int pos = Math.max(s.indexOf('-'), s.indexOf('+'));
103         int len = (pos >= 0) ? pos : s.length();
104 
105         // determine precision
106         Precision precision;
107         if (len >= 14) {
108             precision = Precision.SECOND;
109         } else if (len >= 12) {
110             precision = Precision.MINUTE;
111         } else if (len >= 10) {
112             precision = Precision.HOUR;
113         } else if (len >= 8) {
114             precision = Precision.DAY;
115         } else if (len >= 6) {
116             precision = Precision.MONTH;
117         } else {
118             precision = Precision.YEAR;
119         }
120 
121         // default time zone is UTC
122         if (pos < 0) {
123             s += "+0000";
124         }
125 
126         // parse timestamp
127         try {
128             CommonTS ts = new CommonTS(s);
129             DateTime dateTime = new DateTime(
130                     ts.getYear(),
131                     (ts.getMonth() == 0) ? 1 : ts.getMonth(),
132                     (ts.getDay() == 0) ? 1 : ts.getDay(),
133                     ts.getHour(),
134                     ts.getMinute(),
135                     ts.getSecond(),
136                     DateTimeZone.forOffsetHoursMinutes(
137                             ts.getGMTOffset() / 100,
138                             ts.getGMTOffset() % 100));
139             return new Timestamp(dateTime.toDateTime(DateTimeZone.UTC), precision);
140         } catch (DataTypeException e) {
141             throw new XDSMetaDataException(ValidationMessage.INVALID_TIME, s);
142         }
143     }
144 
145     /**
146      * Returns a HL7 representation of the given timestamp, considering the precision.
147      * @param timestamp
148      *      {@link Timestamp} object, can be <code>null</code>.
149      * @return
150      *      HL7 representation of the timestamp, or <code>null</code> if the parameter is <code>null</code>.
151      */
152     public static String toHL7(Timestamp timestamp) {
153         return (timestamp != null) ? timestamp.toHL7() : null;
154     }
155 
156     private String toHL7() {
157         return FORMATTERS.get(getPrecision()).print(getDateTime().toDateTime(DateTimeZone.UTC));
158     }
159 
160     public void setDateTime(DateTime dateTime) {
161         this.dateTime = (dateTime != null) ? dateTime.toDateTime(DateTimeZone.UTC) : null;
162     }
163 
164     public Precision getPrecision() {
165         return (precision != null) ? precision : Precision.SECOND;
166     }
167 
168 
169     /**
170      * Two HL7 timestamps are equal when they have the same values in the relevant fields
171      * (i.e. in the ones covered by the precision).
172      */
173     @Override
174     public boolean equals(Object o) {
175         if (this == o) return true;
176         if (o == null || getClass() != o.getClass()) return false;
177 
178         Timestamp timestamp = (Timestamp) o;
179         return StringUtils.equals(toHL7(this), toHL7(timestamp));
180     }
181 
182     @Override
183     public int hashCode() {
184         return Objects.hash(dateTime, precision);
185     }
186 
187     @Override
188     public String toString() {
189         return "Timestamp(" +
190                 "dateTime=" + dateTime +
191                 ", precision=" + precision +
192                 ')';
193     }
194 }