All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opencds.cqf.cql.engine.runtime.DateTime Maven / Gradle / Ivy

Go to download

The engine library for the Clinical Quality Language Java reference implementation

The newest version!
package org.opencds.cqf.cql.engine.runtime;

import java.math.BigDecimal;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.Objects;
import org.opencds.cqf.cql.engine.exception.CqlException;
import org.opencds.cqf.cql.engine.exception.InvalidDateTime;

public class DateTime extends BaseTemporal {
    private ZoneOffset zoneOffset;

    private OffsetDateTime dateTime;

    public OffsetDateTime getDateTime() {
        return dateTime;
    }

    public void setDateTime(OffsetDateTime dateTime) {
        if (dateTime.getYear() < 1) {
            throw new InvalidDateTime(
                    String.format("The year: %d falls below the accepted bounds of 0001-9999.", dateTime.getYear()));
        }

        if (dateTime.getYear() > 9999) {
            throw new InvalidDateTime(
                    String.format("The year: %d falls above the accepted bounds of 0001-9999.", dateTime.getYear()));
        }
        this.dateTime = dateTime;
    }

    public DateTime withPrecision(Precision precision) {
        this.precision = precision;
        return this;
    }

    public DateTime(OffsetDateTime dateTime) {
        setDateTime(dateTime);
        this.precision = Precision.MILLISECOND;
        zoneOffset = toZoneOffset(dateTime);
    }

    public DateTime(OffsetDateTime dateTime, Precision precision) {
        setDateTime(dateTime);
        this.precision = precision;
        zoneOffset = toZoneOffset(dateTime);
    }

    public DateTime(String dateString, ZoneOffset offset) {
        if (offset == null) {
            throw new CqlException("Cannot pass a null offset");
        }

        zoneOffset = offset;

        // Handles case when Tz is not complete (T02:04:59.123+01)
        if (dateString.matches("T[0-2]\\d:[0-5]\\d:[0-5]\\d\\.\\d{3}(\\+|-)\\d{2}$")) {
            dateString += ":00";
        }
        int size = 0;
        if (dateString.contains("T")) {
            String[] datetimeSplit = dateString.split("T");
            size += datetimeSplit[0].split("-").length;
            String[] tzSplit = dateString.contains("Z") ? dateString.split("Z") : datetimeSplit[1].split("[+-]");
            size += tzSplit[0].split(":").length;
            if (tzSplit[0].contains(".")) {
                ++size;
            }
            precision = Precision.fromDateTimeIndex(size - 1);
            if (tzSplit.length == 1 && !dateString.contains("Z")) {
                dateString = TemporalHelper.autoCompleteDateTimeString(dateString, precision);
                dateString += offset.getId();
            }
        } else {
            size += dateString.split("-").length;
            precision = Precision.fromDateTimeIndex(size - 1);
            dateString = TemporalHelper.autoCompleteDateTimeString(dateString, precision);
            dateString += offset.getId();
        }

        setDateTime(OffsetDateTime.parse(dateString));
    }

    public DateTime(BigDecimal offset, int... dateElements) {
        if (offset == null) {
            throw new CqlException("BigDecimal offset must be non-null");
        }

        if (dateElements.length == 0) {
            throw new InvalidDateTime("DateTime must include a year");
        }

        zoneOffset = toZoneOffset(offset);

        StringBuilder dateString = new StringBuilder();
        String[] stringElements = TemporalHelper.normalizeDateTimeElements(dateElements);

        for (int i = 0; i < stringElements.length; ++i) {
            if (i == 0) {
                dateString.append(stringElements[i]);
                continue;
            } else if (i < 3) {
                dateString.append("-");
            } else if (i == 3) {
                dateString.append("T");
            } else if (i < 6) {
                dateString.append(":");
            } else if (i == 6) {
                dateString.append(".");
            }
            dateString.append(stringElements[i]);
        }

        precision = Precision.fromDateTimeIndex(stringElements.length - 1);
        dateString =
                new StringBuilder().append(TemporalHelper.autoCompleteDateTimeString(dateString.toString(), precision));
        dateString.append(zoneOffset.getId());
        setDateTime(OffsetDateTime.parse(dateString.toString()));
    }

    public ZoneOffset getZoneOffset() {
        return zoneOffset;
    }

    public DateTime expandPartialMinFromPrecision(Precision precision) {
        OffsetDateTime odt = this.getDateTime().plusYears(0);
        for (int i = precision.toDateTimeIndex() + 1; i < 7; ++i) {
            odt = odt.with(
                    Precision.fromDateTimeIndex(i).toChronoField(),
                    odt.range(Precision.fromDateTimeIndex(i).toChronoField()).getMinimum());
        }
        return new DateTime(odt, this.precision);
    }

    public DateTime expandPartialMin(Precision precision) {
        OffsetDateTime odt = this.getDateTime().plusYears(0);
        return new DateTime(odt, precision == null ? Precision.MILLISECOND : precision);
    }

    public DateTime expandPartialMax(Precision precision) {
        OffsetDateTime odt = this.getDateTime().plusYears(0);
        for (int i = this.getPrecision().toDateTimeIndex() + 1; i < 7; ++i) {
            if (i <= precision.toDateTimeIndex()) {
                odt = odt.with(
                        Precision.fromDateTimeIndex(i).toChronoField(),
                        odt.range(Precision.fromDateTimeIndex(i).toChronoField())
                                .getMaximum());
            } else {
                odt = odt.with(
                        Precision.fromDateTimeIndex(i).toChronoField(),
                        odt.range(Precision.fromDateTimeIndex(i).toChronoField())
                                .getMinimum());
            }
        }
        return new DateTime(odt, precision == null ? Precision.MILLISECOND : precision);
    }

    @Override
    public boolean isUncertain(Precision precision) {
        if (precision == Precision.WEEK) {
            precision = Precision.DAY;
        }

        return this.precision.toDateTimeIndex() < precision.toDateTimeIndex();
    }

    @Override
    public Interval getUncertaintyInterval(Precision precision) {
        DateTime start = expandPartialMin(precision);
        DateTime end = expandPartialMax(precision).expandPartialMinFromPrecision(precision);
        return new Interval(start, true, end, true);
    }

    @Override
    public Integer compare(BaseTemporal other, boolean forSort) {
        boolean differentPrecisions = this.getPrecision() != other.getPrecision();

        if (differentPrecisions) {
            Integer result = this.compareToPrecision(
                    other, Precision.getHighestDateTimePrecision(this.precision, other.precision));
            if (result == null && forSort) {
                return this.precision.toDateTimeIndex() > other.precision.toDateTimeIndex() ? 1 : -1;
            }
            return result;
        } else {
            return compareToPrecision(other, this.precision);
        }
    }

    public OffsetDateTime getNormalized(Precision precision) {
        return getNormalized(precision, zoneOffset);
    }

    public OffsetDateTime getNormalized(Precision precision, ZoneOffset nullableZoneOffset) {
        if (precision.toDateTimeIndex() > Precision.DAY.toDateTimeIndex()) {
            if (nullableZoneOffset != null) {
                return dateTime.withOffsetSameInstant(nullableZoneOffset);
            }

            throw new IllegalStateException("There must be a non-null offset!");
        }

        return dateTime;
    }

    @Override
    public Integer compareToPrecision(BaseTemporal other, Precision precision) {
        boolean leftMeetsPrecisionRequirements = this.precision.toDateTimeIndex() >= precision.toDateTimeIndex();
        boolean rightMeetsPrecisionRequirements = other.precision.toDateTimeIndex() >= precision.toDateTimeIndex();

        // adjust dates to evaluation offset
        OffsetDateTime leftDateTime = this.getNormalized(precision);
        OffsetDateTime rightDateTime = ((DateTime) other).getNormalized(precision, getZoneOffset());

        if (!leftMeetsPrecisionRequirements || !rightMeetsPrecisionRequirements) {
            precision = Precision.getLowestDateTimePrecision(this.precision, other.precision);
        }

        for (int i = 0; i < precision.toDateTimeIndex() + 1; ++i) {
            int leftComp = leftDateTime.get(Precision.getDateTimeChronoFieldFromIndex(i));
            int rightComp = rightDateTime.get(Precision.getDateTimeChronoFieldFromIndex(i));
            if (leftComp > rightComp) {
                return 1;
            } else if (leftComp < rightComp) {
                return -1;
            }
        }

        if (leftMeetsPrecisionRequirements && rightMeetsPrecisionRequirements) {
            return 0;
        }

        return null;
    }

    @Override
    public int compareTo(BaseTemporal other) {
        return this.compare(other, true);
    }

    @Override
    public Boolean equivalent(Object other) {
        Integer comparison = compare((BaseTemporal) other, false);
        return comparison != null && comparison == 0;
    }

    @Override
    public Boolean equal(Object other) {
        Integer comparison = compare((BaseTemporal) other, false);
        return comparison == null ? null : comparison == 0;
    }

    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (other == null || getClass() != other.getClass()) {
            return false;
        }

        final DateTime otherDateTime = (DateTime) other;

        return Objects.equals(zoneOffset, otherDateTime.zoneOffset)
                && Objects.equals(precision, otherDateTime.precision)
                && Objects.equals(dateTime, otherDateTime.dateTime);
    }

    @Override
    public int hashCode() {
        return Objects.hash(zoneOffset, dateTime);
    }

    @Override
    public String toString() {
        switch (precision) {
            case YEAR:
                return String.format("%04d", dateTime.getYear());
            case MONTH:
                return String.format("%04d-%02d", dateTime.getYear(), dateTime.getMonthValue());
            case DAY:
                return String.format(
                        "%04d-%02d-%02d", dateTime.getYear(), dateTime.getMonthValue(), dateTime.getDayOfMonth());
            case HOUR:
                return String.format(
                        "%04d-%02d-%02dT%02d",
                        dateTime.getYear(), dateTime.getMonthValue(), dateTime.getDayOfMonth(), dateTime.getHour());
            case MINUTE:
                return String.format(
                        "%04d-%02d-%02dT%02d:%02d",
                        dateTime.getYear(),
                        dateTime.getMonthValue(),
                        dateTime.getDayOfMonth(),
                        dateTime.getHour(),
                        dateTime.getMinute());
            case SECOND:
                return String.format(
                        "%04d-%02d-%02dT%02d:%02d:%02d",
                        dateTime.getYear(),
                        dateTime.getMonthValue(),
                        dateTime.getDayOfMonth(),
                        dateTime.getHour(),
                        dateTime.getMinute(),
                        dateTime.getSecond());
            default:
                return String.format(
                        "%04d-%02d-%02dT%02d:%02d:%02d.%03d",
                        dateTime.getYear(),
                        dateTime.getMonthValue(),
                        dateTime.getDayOfMonth(),
                        dateTime.getHour(),
                        dateTime.getMinute(),
                        dateTime.getSecond(),
                        dateTime.get(precision.toChronoField()));
        }
    }

    // conversion functions

    public Date toJavaDate() {
        return java.util.Date.from(dateTime.toInstant());
    }

    public String toDateString() {
        return DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(dateTime);
    }

    private ZoneOffset toZoneOffset(OffsetDateTime offsetDateTime) {
        return offsetDateTime.getOffset();
    }

    private ZoneOffset toZoneOffset(BigDecimal offsetAsBigDecimal) {
        if (offsetAsBigDecimal == null) {
            return null;
        }

        return ZoneOffset.ofHoursMinutes(
                offsetAsBigDecimal.intValue(),
                new BigDecimal(60)
                        .multiply(offsetAsBigDecimal.remainder(BigDecimal.ONE))
                        .intValue());
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy