# Historical data

> How-to guide: Read the historical values of a node over time ranges, at specific times, and aggregated, modify and delete them, and read the event history.

> This content was machine translated and may contain errors. [Read in German](https://www.traeger.de/docs/development/opcua/client/historical-data)

**Sample applications:**

- Client.HDA ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Client.HDA), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Client.HDA)): This sample demonstrates how to perform history data access (HDA) on the data provided by the OPC UA server.
- Server.HDA ([C#](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/cs/Basic/Server.HDA), [VB.NET](https://github.com/Traeger-GmbH/opcuanet-samples/tree/master/vb/Basic/Server.HDA)): This sample demonstrates how to setup an OPC UA server to provide HDA to its nodes provided.

Types used: `OpcClient`, `OpcNodeId`, `IOpcNodeHistoryNavigator`, `OpcHistoryValue`, `OpcModifiedHistoryValue`, `OpcAggregateType`

According to the OPC UA specification, every node of the Variable node class can record the values of its Value attribute over time. On every change of the Value attribute, the server then stores the new value together with the timestamp of the Value attribute. These pairs of value and timestamp are called **historical data**. The server itself decides where it stores the data. The client uses the `IsHistorizing` attribute of the node to determine whether the server provides historical data for the node, that is, whether it stores value changes historically. The client selects the node whose historical data it accesses through the NodeId (`OpcNodeId`). The client can read, update, replace, delete, or create historical data. Reading is the most common case. The server determines whether and which of these operations it allows for a node. The client does not have to process all historical values, regardless of whether it reads with or without a navigator.

## Read values of a time range

The client reads historical data over a time range.

Read all values within an **open time range**, in which `StartTime` or `EndTime` is undefined:

**Read all values from a specific timestamp (`StartTime`) onward**:

```csharp
var startTime = new DateTime(2017, 2, 16, 10, 0, 0);
var history = client.ReadNodeHistory(
        startTime, endTime: null, "ns=2;s=Machine/Job/Speed");
```

```vbnet
Dim startTime = New DateTime(2017, 2, 16, 10, 0, 0)
Dim history = client.ReadNodeHistory( _
        startTime, endTime:=Nothing, "ns=2;s=Machine/Job/Speed")
```

**Read all values up to a specific timestamp (`EndTime`)**:

```csharp
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
var history = client.ReadNodeHistory(
        null, endTime, "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)
Dim history = client.ReadNodeHistory( _
        Nothing, endTime, "ns=2;s=Machine/Job/Speed")

```

**Read all values within a closed time range**, in which `StartTime` and `EndTime` are defined:

```csharp
var startTime = new DateTime(2017, 2, 16, 10, 0, 0);
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
 
var history = client.ReadNodeHistory(
        startTime, endTime, "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim startTime = New DateTime(2017, 2, 16, 10, 0, 0)
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)

Dim history = client.ReadNodeHistory( _
        startTime, endTime, "ns=2;s=Machine/Job/Speed")

```

**Process the values** (through an instance that implements the `IEnumerable` interface):

```csharp
foreach (var value in history) {
    Console.WriteLine(
            "{0}: {1}",
            value.Timestamp,
            value);
}

```

```vbnet
For Each value In history
    Console.WriteLine( _
            "{0}: {1}",
            value.Timestamp,
            value)
Next

```

## Read values page by page

When reading historical data page by page, the client retrieves only a specific number of values from the server at a time.

**Read a specific number of values per page**:

```csharp
var historyNavigator = client.ReadNodeHistory(
        10, "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim historyNavigator = client.ReadNodeHistory( _
        10, "ns=2;s=Machine/Job/Speed")

```

Read a specific number of values per page within an **open time range**, in which `StartTime` or `EndTime` is undefined:

**Read a specific number of values per page from a specific timestamp (`StartTime`) onward**:

```csharp
var startTime = new DateTime(2017, 2, 16, 15, 0, 0);
var historyNavigator = client.ReadNodeHistory(
        startTime, 10, "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim startTime = New DateTime(2017, 2, 16, 15, 0, 0)
Dim historyNavigator = client.ReadNodeHistory( _
        startTime, 10, "ns=2;s=Machine/Job/Speed")

```

**Read a specific number of values per page up to a specific timestamp (`EndTime`)**:

```csharp
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
var historyNavigator = client.ReadNodeHistory(
        null, endTime, 10, "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)
Dim historyNavigator = client.ReadNodeHistory( _
        Nothing, endTime, 10, "ns=2;s=Machine/Job/Speed")

```

**Read a specific number of values per page within a closed time range**:

```csharp
var startTime = new DateTime(2017, 2, 16, 10, 0, 0);
var endTime = new DateTime(2017, 2, 16, 15, 0, 0);
 
var historyNavigator = client.ReadNodeHistory(
        startTime, endTime, 10, "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim startTime = New DateTime(2017, 2, 16, 10, 0, 0)
Dim endTime = New DateTime(2017, 2, 16, 15, 0, 0)

Dim historyNavigator = client.ReadNodeHistory( _
        startTime, endTime, 10, "ns=2;s=Machine/Job/Speed")

```

**Process the values** (through an instance that implements the `IOpcNodeHistoryNavigator` interface):

```csharp
do {
    foreach (var value in historyNavigator) {
        Console.WriteLine(
                "{0}: {1}",
                value.Timestamp,
                value);
    }
} while (historyNavigator.MoveNextPage());
 
historyNavigator.Close();

```

```vbnet
Do
    For Each value In historyNavigator
        Console.WriteLine( _
                "{0}: {1}", _
                value.Timestamp, _
                value)
    Next
Loop While historyNavigator.MoveNextPage()

historyNavigator.Close()

```

Always make sure that the `Close` method of the navigator is called. Only then can the server discard the historical data it has buffered for this request. Instead of calling `Close` explicitly, you can also use the navigator in a `using` block:

```csharp
using (historyNavigator) {
    do {
        foreach (var value in historyNavigator) {
            Console.WriteLine(
                    "{0}: {1}",
                    value.Timestamp,
                    value);
        }
    } while (historyNavigator.MoveNextPage());
}

```

```vbnet
Using historyNavigator
    Do
        For Each value In historyNavigator
            Console.WriteLine( _
                    "{0}: {1}", _
                    value.Timestamp, _
                    value)
        Next
    Loop While historyNavigator.MoveNextPage()
End Using

```

The navigator uses at most 999 requests to retrieve a page. If the server has not completed the page by then, the navigator throws an `OpcException` with the status `BadCommunicationError`. This also applies to the navigator of the event history.

## Read the history of multiple nodes

Types used: `OpcClient`, `OpcNodeId`, `IOpcNodeHistoryNavigator`, `OpcHistoryValue`

`ReadNodesHistory` reads the historical values of multiple nodes for the same time range in one call. If you specify a number of values per page, the method returns a navigator. Its indexer returns the values of a single node. The client loads further pages the same way as for a single node:

```csharp
var nodeIds = new OpcNodeId[] { "ns=2;s=Machine/Speed", "ns=2;s=Machine/Temperature" };
 
using (var history = client.ReadNodesHistory(
        DateTime.UtcNow.AddHours(-1),
        DateTime.UtcNow,
        numberOfValues: 100,
        nodeIds)) {
    foreach (OpcHistoryValue value in history[nodeIds[0]])
        Console.WriteLine("{0}: {1}", value.Timestamp, value.Value);
}

```

```vbnet
Dim nodeIds = New OpcNodeId() {"ns=2;s=Machine/Speed", "ns=2;s=Machine/Temperature"}

Using history = client.ReadNodesHistory( _
        DateTime.UtcNow.AddHours(-1), _
        DateTime.UtcNow, _
        numberOfValues:=100, _
        nodeIds)
    For Each value As OpcHistoryValue In history(nodeIds(0))
        Console.WriteLine("{0}: {1}", value.Timestamp, value.Value)
    Next
End Using
```

Without a number per page, `ReadNodesHistory` returns all values of the time range as a dictionary with the identifier of the node as the key:

```csharp
IDictionary<OpcNodeId, IEnumerable<OpcHistoryValue>> history = client.ReadNodesHistory(
        DateTime.UtcNow.AddHours(-1),
        DateTime.UtcNow,
        "ns=2;s=Machine/Speed",
        "ns=2;s=Machine/Temperature");
 
foreach (var entry in history)
    Console.WriteLine("{0}: {1} values", entry.Key, entry.Value.Count());

```

```vbnet
Dim history As IDictionary(Of OpcNodeId, IEnumerable(Of OpcHistoryValue)) = _
        client.ReadNodesHistory( _
                DateTime.UtcNow.AddHours(-1), _
                DateTime.UtcNow, _
                "ns=2;s=Machine/Speed", _
                "ns=2;s=Machine/Temperature")

For Each entry In history
    Console.WriteLine("{0}: {1} values", entry.Key, entry.Value.Count())
Next
```

## Read aggregated values

For processed reading of historical data, you select one of several kinds of aggregation through `OpcAggregateType`.

**Read the minimum value within a time range**:

```csharp
var minSpeed = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Minimum,
        "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim minSpeed = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Minimum, _
        "ns=2;s=Machine/Job/Speed")

```

**Read the average value within a time range**:

```csharp
var avgSpeed = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Average,
        "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim avgSpeed = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Average, _
        "ns=2;s=Machine/Job/Speed")

```

**Read the maximum value within a time range**:

```csharp
var maxSpeed = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Maximum,
        "ns=2;s=Machine/Job/Speed");

```

```vbnet
Dim maxSpeed = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Maximum, _
        "ns=2;s=Machine/Job/Speed")

```

If you divide the values into intervals of equal length, the server returns one aggregated value per interval. You specify the length of the intervals in milliseconds. Without it, the server calculates one value over the entire time range:

**Read the average value per hour within a time range**:

```csharp
// Read the average value of each hour within the time window.
var avgSpeedPerHour = client.ReadNodeHistoryProcessed(
        startTime,
        endTime,
        OpcAggregateType.Average,
        aggregateInterval: TimeSpan.FromHours(1).TotalMilliseconds,
        "ns=2;s=Machine/Job/Speed");
```

```vbnet
'Read the average value of each hour within the time window.
Dim avgSpeedPerHour = client.ReadNodeHistoryProcessed( _
        startTime, _
        endTime, _
        OpcAggregateType.Average, _
        aggregateInterval:=TimeSpan.FromHours(1).TotalMilliseconds, _
        "ns=2;s=Machine/Job/Speed")
```

Aggregated values carry only the source timestamp (SourceTimestamp), not a server timestamp.

## Read values at specific times

When the client reads values at specific times, the server returns one value per point in time. If there is no recorded value at a point in time, the server usually determines the value from the neighboring values:

```csharp
var times = new DateTime[] {
    new DateTime(2017, 2, 16, 10, 0, 0),
    new DateTime(2017, 2, 16, 12, 0, 0)
};
 
var speeds = client.ReadNodeHistoryAtTime(times, "ns=2;s=Machine/Job/Speed");
 
foreach (var value in speeds)
    Console.WriteLine("{0}: {1}", value.Timestamp, value);

```

```vbnet
Dim times = New DateTime() { _
    New DateTime(2017, 2, 16, 10, 0, 0), _
    New DateTime(2017, 2, 16, 12, 0, 0) _
}

Dim speeds = client.ReadNodeHistoryAtTime(times, "ns=2;s=Machine/Job/Speed")

For Each value In speeds
    Console.WriteLine("{0}: {1}", value.Timestamp, value)
Next
```

## Read modified values

If a client has updated, replaced, or deleted historical values (section [Modify historical data](https://www.traeger.de/en/docs/development/opcua/client/historical-data#modify-historical-data)), a server can keep the previous values together with information about the modification. The `ReadNodeHistoryModified` method returns these values as instances of `OpcModifiedHistoryValue`. As with the other methods, the client can also read the values page by page:

```csharp
var modifiedSpeeds = client.ReadNodeHistoryModified(
        startTime,
        endTime,
        "ns=2;s=Machine/Job/Speed");
 
foreach (var value in modifiedSpeeds) {
    Console.WriteLine(
            "{0}: {1} ({2} at {3} by {4})",
            value.Timestamp,
            value,
            value.ModificationType,
            value.ModificationTime,
            value.ModificationUserName);
}
```

```vbnet
Dim modifiedSpeeds = client.ReadNodeHistoryModified( _
        startTime, _
        endTime, _
        "ns=2;s=Machine/Job/Speed")

For Each value In modifiedSpeeds
    Console.WriteLine( _
            "{0}: {1} ({2} at {3} by {4})", _
            value.Timestamp, _
            value, _
            value.ModificationType, _
            value.ModificationTime, _
            value.ModificationUserName)
Next
```

Against a server that uses `OpcNodeHistorian`, `ReadNodeHistoryModified` and `DeleteNodeHistoryModified` refer to the timestamps of the values, not to the time of the modification.

If the server rejects the request for the node, `ReadNodeHistoryProcessed`, `ReadNodeHistoryAtTime`, and `ReadNodeHistoryModified` throw an `OpcException` with the status of the server, for example `BadAggregateNotSupported`. If the server accepts the node but returns no values, the result is empty.

## Modify historical data

Types used: `OpcClient`, `OpcNodeId`, `OpcValue`, `OpcStatus`, `OpcStatusCollection`

To modify historical data, the client passes the values as instances of `OpcValue`. Their `SourceTimestamp` defines the point in time of the value. The server returns an `OpcStatus` for each value. OPC UA defines the following rules:

- `CreateNodeHistory` adds values only at points in time for which no value exists yet.
- `ReplaceNodeHistory` replaces only existing values.
- `UpdateNodeHistory` adds values or replaces existing values.

**Add new values**:

```csharp
OpcStatusCollection results = client.CreateNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        new OpcValue(1200, sourceTimestamp: new DateTime(2017, 2, 16, 10, 0, 0)),
        new OpcValue(1300, sourceTimestamp: new DateTime(2017, 2, 16, 10, 5, 0)));
 
foreach (var result in results) {
    if (result.IsBad)
        Console.WriteLine("Failed to create value: {0}", result.Description);
}

```

```vbnet
Dim results As OpcStatusCollection = client.CreateNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        New OpcValue(1200, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 0, 0)), _
        New OpcValue(1300, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 5, 0)))

For Each result In results
    If result.IsBad Then
        Console.WriteLine("Failed to create value: {0}", result.Description)
    End If
Next
```

**Replace an existing value**:

```csharp
OpcStatus result = client.ReplaceNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        new OpcValue(1250, sourceTimestamp: new DateTime(2017, 2, 16, 10, 0, 0)));
```

```vbnet
Dim result As OpcStatus = client.ReplaceNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        New OpcValue(1250, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 0, 0)))
```

**Add or replace a value**:

```csharp
OpcStatus result = client.UpdateNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        new OpcValue(1250, sourceTimestamp: new DateTime(2017, 2, 16, 10, 0, 0)));
```

```vbnet
Dim result As OpcStatus = client.UpdateNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        New OpcValue(1250, sourceTimestamp:=New DateTime(2017, 2, 16, 10, 0, 0)))
```

**Delete the values of a time range or at specific times**:

```csharp
// Delete all values within the time window.
OpcStatusCollection results = client.DeleteNodeHistory(
        "ns=2;s=Machine/Job/Speed",
        startTime,
        endTime);
 
// Delete the value of the timestamp specified.
OpcStatus result = client.DeleteNodeHistoryAtTime(
        "ns=2;s=Machine/Job/Speed",
        time: new DateTime(2017, 2, 16, 10, 5, 0));
```

```vbnet
'Delete all values within the time window.
Dim results As OpcStatusCollection = client.DeleteNodeHistory( _
        "ns=2;s=Machine/Job/Speed", _
        startTime, _
        endTime)

'Delete the value of the timestamp specified.
Dim result As OpcStatus = client.DeleteNodeHistoryAtTime( _
        "ns=2;s=Machine/Job/Speed", _
        time:=New DateTime(2017, 2, 16, 10, 5, 0))
```

## Read the event history

Types used: `OpcClient`, `OpcNodeId`, `OpcEventFilter`, `OpcEvent`, `IOpcEventHistoryNavigator`

Besides the values of variables, a server can also record past events. The client reads the event history through the node that reports the events, for example, the server node or an event node (page [Events](https://www.traeger.de/en/docs/development/opcua/client/events)). An event filter determines which events and which fields the server returns. You create it the same way as when subscribing to events (section [Filter events](https://www.traeger.de/en/docs/development/opcua/client/events#filter-events)):

```csharp
var severity = new OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity");
 
var filter = OpcFilter.Using(client)
        .FromEvents(OpcEventTypes.AlarmCondition)
        .Where(severity > OpcEventSeverity.Medium)
        .Select();
 
IEnumerable<OpcEvent> events = client.ReadEventHistory(
        startTime,
        endTime,
        "ns=2;s=Machine",
        filter);
 
foreach (var historicalEvent in events)
    Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message);

```

```vbnet
Dim severity = New OpcSimpleAttributeOperand(OpcEventTypes.Event, "Severity")

Dim filter = OpcFilter.Using(client) _
        .FromEvents(OpcEventTypes.AlarmCondition) _
        .Where(severity > OpcEventSeverity.Medium) _
        .Select()

Dim events As IEnumerable(Of OpcEvent) = client.ReadEventHistory( _
        startTime, _
        endTime, _
        "ns=2;s=Machine", _
        filter)

For Each historicalEvent In events
    Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message)
Next
```

**Read the events page by page**: As with historical values, a navigator returns the events page by page. Here, too, make sure that the navigator is closed, for example, with a `using` block:

```csharp
using (var eventNavigator = client.ReadEventHistory(
        startTime,
        endTime,
        numberOfEvents: 10,
        "ns=2;s=Machine",
        filter)) {
    do {
        foreach (var historicalEvent in eventNavigator)
            Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message);
    } while (eventNavigator.MoveNextPage());
}

```

```vbnet
Using eventNavigator = client.ReadEventHistory( _
        startTime, _
        endTime, _
        numberOfEvents:=10, _
        "ns=2;s=Machine", _
        filter)
    Do
        For Each historicalEvent In eventNavigator
            Console.WriteLine("{0}: {1}", historicalEvent.Time, historicalEvent.Message)
        Next
    Loop While eventNavigator.MoveNextPage()
End Using
```

A server is not required to provide past values or events.

## Historical data of an OPC HDA server

The client reads the historical data of an OPC HDA server (Historical Data Access) with the same methods. The section [Historical data (HDA)](https://www.traeger.de/en/docs/development/opcua/client/opc-classic#historical-data-hda) on the OPC Classic page describes the procedure.
