/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.timeseries.precise.zdt; import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.NoSuchElementException; import java.util.Objects; import org.threeten.bp.ZoneId; import org.threeten.bp.ZoneOffset; import org.threeten.bp.ZonedDateTime; import com.opengamma.timeseries.DoubleTimeSeries; import com.opengamma.timeseries.DoubleTimeSeriesOperators.BinaryOperator; import com.opengamma.timeseries.DoubleTimeSeriesOperators.UnaryOperator; import com.opengamma.timeseries.precise.PreciseDoubleTimeSeries; /** * Standard immutable implementation of {@code ZonedDateTimeDoubleTimeSeries}. */ public final class ImmutableZonedDateTimeDoubleTimeSeries extends AbstractZonedDateTimeDoubleTimeSeries implements ZonedDateTimeDoubleTimeSeries, Serializable { /** Serialization version. */ private static final long serialVersionUID = -43654613865187568L; /** * The time-zone. */ private final ZoneId _zone; /** * The times in the series. */ private final long[] _times; /** * The values in the series. */ private final double[] _values; //------------------------------------------------------------------------- /** * Creates an empty builder, used to create time-series. * <p> * The builder has methods to create and modify a time-series. * * @param zone the time-zone, not null * @return the time-series builder, not null */ public static ZonedDateTimeDoubleTimeSeriesBuilder builder(ZoneId zone) { return new ImmutableZonedDateTimeDoubleTimeSeriesBuilder(zone); } //------------------------------------------------------------------------- /** * Obtains an empty time-series. * * @param zone the time-zone, not null * @return the time-series, not null */ public static ImmutableZonedDateTimeDoubleTimeSeries ofEmpty(ZoneId zone) { Objects.requireNonNull(zone, "zone"); return new ImmutableZonedDateTimeDoubleTimeSeries(new long[0], new double[0], zone); } /** * Obtains an empty time-series using a UTC zone. * * @return the time-series, not null */ public static ImmutableZonedDateTimeDoubleTimeSeries ofEmptyUTC() { return ofEmpty(ZoneOffset.UTC); } /** * Obtains a time-series from a single instant and value. * * @param instant the singleton instant, not null * @param value the singleton value * @return the time-series, not null */ public static ImmutableZonedDateTimeDoubleTimeSeries of(ZonedDateTime instant, double value) { Objects.requireNonNull(instant, "instant"); long[] timesArray = new long[] {ZonedDateTimeToLongConverter.convertToLong(instant)}; double[] valuesArray = new double[] {value}; return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, instant.getZone()); } /** * Obtains a time-series from matching arrays of instants and values. * * @param instants the date array, not null * @param values the value array, not null * @param zone the time-zone, may be null if the arrays are non-empty * @return the time-series, not null * @throws IllegalArgumentException if the arrays are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries of(ZonedDateTime[] instants, Double[] values, ZoneId zone) { long[] timesArray = convertToLongArray(instants); double[] valuesArray = convertToDoubleArray(values); validate(timesArray, valuesArray); zone = (zone != null ? zone : instants[0].getZone()); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, zone); } /** * Obtains a time-series from matching arrays of instants and values, using a UTC zone. * * @param instants the instant array, not null * @param values the value array, not null * @return the time-series, not null * @throws IllegalArgumentException if the arrays are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries ofUTC(ZonedDateTime[] instants, Double[] values) { return of(instants, values, ZoneOffset.UTC); } /** * Obtains a time-series from matching arrays of instants and values. * * @param instants the instant array, not null * @param values the value array, not null * @param zone the time-zone, may be null if the arrays are non-empty * @return the time-series, not null * @throws IllegalArgumentException if the arrays are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries of(ZonedDateTime[] instants, double[] values, ZoneId zone) { long[] timesArray = convertToLongArray(instants); validate(timesArray, values); double[] valuesArray = values.clone(); zone = (zone != null ? zone : instants[0].getZone()); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, zone); } /** * Obtains a time-series from matching arrays of instants and values, using a UTC zone. * * @param instants the instant array, not null * @param values the value array, not null * @return the time-series, not null * @throws IllegalArgumentException if the arrays are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries ofUTC(ZonedDateTime[] instants, double[] values) { return of(instants, values, ZoneOffset.UTC); } /** * Obtains a time-series from matching arrays of instants and values. * * @param instants the instant array, not null * @param values the value array, not null * @param zone the time-zone, not null * @return the time-series, not null * @throws IllegalArgumentException if the arrays are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries of(long[] instants, double[] values, ZoneId zone) { validate(instants, values); Objects.requireNonNull(zone); long[] timesArray = instants.clone(); double[] valuesArray = values.clone(); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, zone); } /** * Obtains a time-series from matching arrays of instants and values, using a UTC zone. * * @param instants the instant array, not null * @param values the value array, not null * @return the time-series, not null * @throws IllegalArgumentException if the arrays are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries ofUTC(long[] instants, double[] values) { return of(instants, values, ZoneOffset.UTC); } /** * Obtains a time-series from matching arrays of instants and values. * * @param instants the instant list, not null * @param values the value list, not null * @param zone the time-zone, may be null if the collections are non-empty * @return the time-series, not null * @throws IllegalArgumentException if the collections are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries of(Collection<ZonedDateTime> instants, Collection<Double> values, ZoneId zone) { long[] timesArray = convertToLongArray(instants); double[] valuesArray = convertToDoubleArray(values); zone = (zone != null ? zone : instants.iterator().next().getZone()); validate(timesArray, valuesArray); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, zone); } /** * Obtains a time-series from matching arrays of instants and values, using a UTC zone. * * @param instants the instant list, not null * @param values the value list, not null * @return the time-series, not null * @throws IllegalArgumentException if the arrays are of different lengths */ public static ImmutableZonedDateTimeDoubleTimeSeries ofUTC(Collection<ZonedDateTime> instants, Collection<Double> values) { return of(instants, values, ZoneOffset.UTC); } /** * Obtains a time-series from another time-series. * * @param timeSeries the time-series, not null * @param zone the time-zone, not null * @return the time-series, not null */ public static ImmutableZonedDateTimeDoubleTimeSeries of(PreciseDoubleTimeSeries<?> timeSeries, ZoneId zone) { Objects.requireNonNull(zone, "zone"); if (timeSeries instanceof ImmutableZonedDateTimeDoubleTimeSeries && ((ImmutableZonedDateTimeDoubleTimeSeries) timeSeries).getZone().equals(zone)) { return (ImmutableZonedDateTimeDoubleTimeSeries) timeSeries; } PreciseDoubleTimeSeries<?> other = (PreciseDoubleTimeSeries<?>) timeSeries; long[] timesArray = other.timesArrayFast(); double[] valuesArray = other.valuesArrayFast(); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, zone); } //------------------------------------------------------------------------- /** * Obtains a time-series from another time-series. * * @param timeSeries the time-series, not null * @param zone the time-zone, not null * @return the time-series, not null */ public static ImmutableZonedDateTimeDoubleTimeSeries from(DoubleTimeSeries<ZonedDateTime> timeSeries, ZoneId zone) { Objects.requireNonNull(zone, "zone"); if (timeSeries instanceof PreciseDoubleTimeSeries) { return of((PreciseDoubleTimeSeries<?>) timeSeries, zone); } long[] timesArray = convertToLongArray(timeSeries.timesArray()); double[] valuesArray = timeSeries.valuesArrayFast(); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, zone); } //------------------------------------------------------------------------- /** * Validates the data before creation. * * @param instants the times, not null * @param values the values, not null */ private static void validate(long[] instants, double[] values) { if (instants == null || values == null) { throw new NullPointerException("Array must not be null"); } // check lengths if (instants.length != values.length) { throw new IllegalArgumentException("Arrays are of different sizes: " + instants.length + ", " + values.length); } // check dates are ordered long maxTime = Long.MIN_VALUE; for (long time : instants) { if (time < maxTime) { throw new IllegalArgumentException("ZonedDateTimes must be ordered"); } maxTime = time; } } /** * Creates an instance. * * @param instants the times, not null * @param values the values, not null * @param zone the time-zone, not null */ ImmutableZonedDateTimeDoubleTimeSeries(long[] instants, double[] values, ZoneId zone) { _times = instants; _values = values; _zone = zone; } //------------------------------------------------------------------------- @Override long[] timesArrayFast0() { return _times; } @Override double[] valuesArrayFast0() { return _values; } @Override ZonedDateTimeDoubleTimeSeries newInstanceFast(long[] instant, double[] values) { return new ImmutableZonedDateTimeDoubleTimeSeries(instant, values, _zone); } //------------------------------------------------------------------------- @Override public ZoneId getZone() { return _zone; } @Override public ZonedDateTimeDoubleTimeSeries withZone(ZoneId zone) { Objects.requireNonNull(zone, "zone"); if (zone.equals(_zone)) { return this; } // immutable, so can share arrays return new ImmutableZonedDateTimeDoubleTimeSeries(_times, _values, zone); } //------------------------------------------------------------------------- @Override public int size() { return _times.length; } //------------------------------------------------------------------------- @Override public boolean containsTime(long instant) { int binarySearch = Arrays.binarySearch(_times, instant); return (binarySearch >= 0); } @Override public Double getValue(long instant) { int binarySearch = Arrays.binarySearch(_times, instant); if (binarySearch >= 0) { return _values[binarySearch]; } else { return null; } } @Override public long getTimeAtIndexFast(int index) { return _times[index]; } @Override public double getValueAtIndexFast(int index) { return _values[index]; } //------------------------------------------------------------------------- @Override public long getEarliestTimeFast() { try { return _times[0]; } catch (IndexOutOfBoundsException ex) { throw new NoSuchElementException("Series is empty"); } } @Override public double getEarliestValueFast() { try { return _values[0]; } catch (IndexOutOfBoundsException ex) { throw new NoSuchElementException("Series is empty"); } } @Override public long getLatestTimeFast() { try { return _times[_times.length - 1]; } catch (IndexOutOfBoundsException ex) { throw new NoSuchElementException("Series is empty"); } } @Override public double getLatestValueFast() { try { return _values[_values.length - 1]; } catch (IndexOutOfBoundsException ex) { throw new NoSuchElementException("Series is empty"); } } //------------------------------------------------------------------------- @Override public long[] timesArrayFast() { return _times.clone(); } @Override public double[] valuesArrayFast() { return _values.clone(); } //------------------------------------------------------------------------- @Override public ZonedDateTimeDoubleTimeSeries subSeriesFast(long startTime, boolean includeStart, long endTime, boolean includeEnd) { if (endTime < startTime) { throw new IllegalArgumentException("Invalid subSeries: endTime < startTime"); } // special case for start equals end if (startTime == endTime) { if (includeStart && includeEnd) { int pos = Arrays.binarySearch(_times, startTime); if (pos >= 0) { return new ImmutableZonedDateTimeDoubleTimeSeries(new long[] {startTime}, new double[] {_values[pos]}, _zone); } } return ofEmpty(_zone); } // special case when this is empty if (isEmpty()) { return ofEmpty(_zone); } // normalize to include start and exclude end if (includeStart == false) { startTime++; } if (includeEnd) { if (endTime != Long.MAX_VALUE) { endTime++; } } // calculate int startPos = Arrays.binarySearch(_times, startTime); startPos = startPos >= 0 ? startPos : -(startPos + 1); int endPos = Arrays.binarySearch(_times, endTime); endPos = endPos >= 0 ? endPos : -(endPos + 1); if (includeEnd && endTime == Long.MAX_VALUE) { endPos = _times.length; } long[] timesArray = Arrays.copyOfRange(_times, startPos, endPos); double[] valuesArray = Arrays.copyOfRange(_values, startPos, endPos); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, _zone); } //------------------------------------------------------------------------- @Override public ZonedDateTimeDoubleTimeSeries head(int numItems) { if (numItems == size()) { return this; } long[] timesArray = Arrays.copyOfRange(_times, 0, numItems); double[] valuesArray = Arrays.copyOfRange(_values, 0, numItems); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, _zone); } @Override public ZonedDateTimeDoubleTimeSeries tail(int numItems) { int size = size(); if (numItems == size) { return this; } long[] timesArray = Arrays.copyOfRange(_times, size - numItems, size); double[] valuesArray = Arrays.copyOfRange(_values, size - numItems, size); return new ImmutableZonedDateTimeDoubleTimeSeries(timesArray, valuesArray, _zone); } //------------------------------------------------------------------------- @Override public ImmutableZonedDateTimeDoubleTimeSeries newInstance(ZonedDateTime[] instants, Double[] values) { return of(instants, values, _zone); } //------------------------------------------------------------------------- @Override public ZonedDateTimeDoubleTimeSeries operate(UnaryOperator operator) { double[] valuesArray = valuesArrayFast(); for (int i = 0; i < valuesArray.length; i++) { valuesArray[i] = operator.operate(valuesArray[i]); } return new ImmutableZonedDateTimeDoubleTimeSeries(_times, valuesArray, _zone); // immutable, so can share times } @Override public ZonedDateTimeDoubleTimeSeries operate(double other, BinaryOperator operator) { double[] valuesArray = valuesArrayFast(); for (int i = 0; i < valuesArray.length; i++) { valuesArray[i] = operator.operate(valuesArray[i], other); } return new ImmutableZonedDateTimeDoubleTimeSeries(_times, valuesArray, _zone); // immutable, so can share times } //------------------------------------------------------------------------- @Override public ZonedDateTimeDoubleTimeSeriesBuilder toBuilder() { return builder(_zone).putAll(this); } }