/* ======================================================================== * Copyright (c) 2005-2020 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using System.Collections.Generic; using System.Text; namespace Opc.Ua.Aggregates { /// /// Represents one aggregation interval within an aggregation query. /// public abstract class TimeSlice { /// /// Start date of the time slice, which may be later than the end date. /// public DateTime From { get; set; } /// /// End date of the time slice, which may be earlier than the start date. /// public DateTime To { get; set; } /// /// Indicates that the timeslice is not as long as requested because the /// EndTime of the aggregation query occurred. /// public bool Incomplete { get; set; } /// /// Enumerator over the raw points that fall in this TimeSlice. /// public IEnumerable Values { get { return Accumulator; } } /// /// Collection of raw points that fall in this TimeSlice. /// protected List Accumulator = new List(); /// /// Value and provenance of a value at the earliest time of this slice /// public BoundingValue EarlyBound { get; set; } /// /// Value and provenance of a value at the latest time of this slice /// public BoundingValue LateBound { get; set; } /// /// Converts the floating-point representation of the millisecond interval into a count /// of 100-nanosecond ticks. /// /// /// protected static long MillisecondsToTicks(double millis) { return (long) Math.Round(millis * 10000); } /// /// Create the first (or only) TimeSlice in a sequence. /// /// /// /// /// public static TimeSlice CreateInitial(DateTime from, DateTime to, double inc) { TimeSlice retval = null; bool incomplete = false; DateTime later; if (from > to) { later = from; if (inc > 0.0) { long intMillis = MillisecondsToTicks(inc); long totMillis = MillisecondsToTicks((from - to).TotalMilliseconds); long remMillis = totMillis % intMillis; later = (remMillis > 0) ? to + new TimeSpan(remMillis) : to + new TimeSpan(intMillis); incomplete = (remMillis > 0); } retval = new BackwardTimeSlice { From = later, To = to, Incomplete = incomplete, EarlyBound = new BoundingValue { Timestamp = to }, LateBound = new BoundingValue { Timestamp = later } }; } else if (to > from) { later = to; if (inc > 0.0) { later = from + new TimeSpan(MillisecondsToTicks(inc)); if (later > to) { later = to; incomplete = true; } } retval = new ForwardTimeSlice { From = from, To = later, Incomplete = incomplete, EarlyBound = new BoundingValue { Timestamp = from }, LateBound = new BoundingValue { Timestamp = later } }; } //deliberately ignore (if from == to) return retval; } /// /// For the given predecessor, create the next TimeSlice in the sequence. /// /// /// /// /// public static TimeSlice CreateNext(DateTime latest, double inc, TimeSlice predecessor) { return predecessor.CreateSuccessor(latest, inc); } /// /// Used to determine whether there is a raw data point whose time stamp exactly matches /// the TimeSlice. If so, the processed data point may need to indicate that it is /// a raw value. /// /// /// public bool ExactMatch(DateTime timestamp) { return timestamp.Equals(From); } /// /// Used to determine whether there is a raw data point whose time stamp exactly matches /// the end of the TimeSlice. /// /// /// public bool EndMatch(DateTime timestamp) { return timestamp.Equals(To); } /// /// Tests to see whether a raw value can be added to this TimeSlice and adds it if so. /// /// /// true if the data value was added public bool AcceptValue(DataValue rawValue) { if (ContainsTime(rawValue.SourceTimestamp)) { Accumulator.Add(rawValue); if (rawValue.StatusCode.Equals(StatusCodes.BadNoData)) this.Incomplete = true; return true; } else { return false; } } /// /// Create the TimeSlice that immediately follows this one in time. /// /// /// /// protected abstract TimeSlice CreateSuccessor(DateTime latest, double inc); /// /// Tests whether a DateTime falls within the TimeSlice /// /// /// true if the DateTime is within the TimeSlice public abstract bool ContainsTime(DateTime time); /// /// Used to determine whether we might be able to release a processed data point for /// this TimeSlice, given the timestamp from the most recent raw data point. /// /// /// public abstract bool Releasable(DateTime timestamp); } /// /// A TimeSlice for forward motion through time. /// public class ForwardTimeSlice : TimeSlice { /// /// Create the TimeSlice that immediately follows this one in time. /// /// /// /// protected override TimeSlice CreateSuccessor(DateTime latest, double inc) { TimeSlice retval = null; if (To < latest) { bool incomplete = false; DateTime target = To + new TimeSpan(MillisecondsToTicks(inc)); if (target > latest) { target = latest; incomplete = true; } retval = new ForwardTimeSlice { From = this.To, To = target, Incomplete = incomplete, EarlyBound = this.LateBound, LateBound = new BoundingValue { Timestamp = target } }; } return retval; } /// /// Tests whether a DateTime falls within the TimeSlice /// /// /// true if the DateTime is within the TimeSlice public override bool ContainsTime(DateTime time) { return ((time >= From) && (time < To)); } /// /// Used to determine whether we might be able to release a processed data point for /// this TimeSlice, given the timestamp from the most recent raw data point. /// /// /// public override bool Releasable(DateTime timestamp) { return timestamp >= To; } } /// /// A TimeSlice for backward motion through time. /// public class BackwardTimeSlice : TimeSlice { /// /// Create the TimeSlice that immediately follows this one in time. /// /// /// /// protected override TimeSlice CreateSuccessor(DateTime latest, double inc) { TimeSlice retval = null; if (From < latest) { DateTime target = From + new TimeSpan(MillisecondsToTicks(inc)); retval = new BackwardTimeSlice { From = target, To = this.From, Incomplete = false, EarlyBound = this.LateBound, LateBound = new BoundingValue { Timestamp = target } }; } return retval; } /// /// Tests whether a DateTime falls within the TimeSlice /// /// /// true if the DateTime is within the TimeSlice public override bool ContainsTime(DateTime time) { return ((time <= From) && (time > To)); } /// /// Used to determine whether we might be able to release a processed data point for /// this TimeSlice, given the timestamp from the most recent raw data point. /// /// /// public override bool Releasable(DateTime timestamp) { return timestamp > From; } } }