Skip to content

Commit fecf175

Browse files
committed
Add ToString() overloads so it's easier to format values for different cultures.
1 parent 431f985 commit fecf175

File tree

7 files changed

+189
-17
lines changed

7 files changed

+189
-17
lines changed

DSMRParser.Tests/TelegramTests.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using DSMRParser.Models;
2+
using Microsoft.VisualStudio.TestTools.UnitTesting;
3+
using System.Globalization;
4+
using System.Linq;
5+
6+
namespace DSMRParser.Tests;
7+
8+
[TestClass]
9+
public class TelegramTests
10+
{
11+
[TestMethod]
12+
public void ToString_Should_FormatCorrectly()
13+
{
14+
var telegram = new Telegram("foo", [
15+
(OBISRegistry.VoltageL1.Id, ["1234.56*V"]),
16+
(OBISRegistry.GasDelivered.Id, ["201211100907S", "6789.12*m3"]),
17+
(OBISRegistry.ElectricityFailures.Id, ["42"]),
18+
(OBISRegistry.ElectricityFailureLog.Id, ["000104180320W","0000237126*s","000101000001W","2147583646*s","200102010203W","2317482647*s"]),
19+
]);
20+
21+
Assert.AreEqual("1234.56V", telegram.VoltageL1!.ToString());
22+
Assert.AreEqual("2020-12-11T10:09:07.0000000+01:00: 6789.12m³", telegram.GasDelivered!.ToString());
23+
Assert.AreEqual("2000-01-01T00:00:01.0000000+01:00: 24856.07:00:46", telegram.ElectricityFailureLog.ToArray()[0].ToString());
24+
25+
var nl_culture = CultureInfo.GetCultureInfo(1043);
26+
Assert.AreEqual("1234,56V", telegram.VoltageL1!.ToString(nl_culture));
27+
Assert.AreEqual("2020-12-11T10:09:07.0000000+01:00: 6789,12m³", telegram.GasDelivered!.ToString(nl_culture));
28+
Assert.AreEqual("11-12-20 10:09:07: 6789,12m³", telegram.GasDelivered!.ToString(null, "dd-MM-yy HH:mm:ss", nl_culture));
29+
30+
var us_culture = CultureInfo.GetCultureInfo(1033);
31+
Assert.AreEqual("1234.56V", telegram.VoltageL1!.ToString(us_culture));
32+
Assert.AreEqual("2020-12-11T10:09:07.0000000+01:00: 6789.12m³", telegram.GasDelivered!.ToString(us_culture));
33+
Assert.AreEqual("12/11/20 10:09:07: 6789.12m³", telegram.GasDelivered!.ToString(null, "MM/dd/yy HH:mm:ss", us_culture));
34+
}
35+
}

DSMRParser/Models/IUnitValue.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
using System;
2+
3+
namespace DSMRParser.Models;
4+
5+
internal interface IUnitValue
6+
{
7+
string ToString(string? format, IFormatProvider? provider);
8+
}

DSMRParser/Models/OBISUnit.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/// </summary>
66
public enum OBISUnit
77
{
8-
/// <summary>Unit-less</summary>
8+
/// <summary>Unitless</summary>
99
NONE,
1010
/// <summary>Watts</summary>
1111
W,
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
namespace DSMRParser.Models;
2+
3+
/// <summary>
4+
/// Extension methods for <see cref="OBISUnit"/>.
5+
/// </summary>
6+
public static class OBISUnitExtensions
7+
{
8+
/// <summary>
9+
/// Converts the <see cref="OBISUnit"/> to a string.
10+
/// </summary>
11+
/// <param name="unit">The <see cref="OBISUnit"/> to convert.</param>
12+
/// <returns>Returns a string representing the <see cref="OBISUnit"/>.</returns>
13+
public static string ToUnitString(this OBISUnit unit)
14+
=> unit switch
15+
{
16+
OBISUnit.NONE => string.Empty,
17+
OBISUnit.m3 => "m³",
18+
OBISUnit.dm3 => "dm³",
19+
_ => unit.ToString()
20+
};
21+
}

DSMRParser/Models/Telegram.cs

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ public class Telegram(string? identification, IEnumerable<(OBISId obisid, IEnume
6363
/// <summary>Number of long power failures in any phase.</summary>
6464
public int? ElectricityLongFailures => ParseInt(OBISRegistry.ElectricityLongFailures);
6565
/// <summary>Power Failure Event Log.</summary>
66-
public IEnumerable<TimeStampedValue<TimeSpan>> ElectricityFailureLog => ParseTimeStampedValues(OBISRegistry.ElectricityFailureLog, (d, v) => TimeSpan.FromSeconds(ParseLongUnit(d, v)?.Value ?? 0), 2);
66+
public IEnumerable<TimeStampedValue<TimeSpan>> ElectricityFailureLog
67+
=> ParseTimeStampedValues(OBISRegistry.ElectricityFailureLog, (d, v) => TimeSpan.FromSeconds(ParseLongUnit(d, v)?.Value ?? 0), 2);
68+
6769
/// <summary>Number of voltage sags in phase L1.</summary>
6870
public int? ElectricitySagsL1 => ParseInt(OBISRegistry.ElectricitySagsL1);
6971
/// <summary>Number of voltage sags in phase L2.</summary>
@@ -213,7 +215,8 @@ protected static bool TryParseDateTimeOffsetCore(string? value, out DateTimeOffs
213215
/// <paramref name="result"/> contains a valid <see cref="byte"/> or null when the method returns false.
214216
/// </param>
215217
/// <returns>True if the parse operation was successful; otherwise, false.</returns>
216-
protected static bool TryParseHexByteCore(string? value, out byte result) => byte.TryParse(value, NumberStyles.HexNumber, _culture, out result);
218+
protected static bool TryParseHexByteCore(string? value, out byte result)
219+
=> byte.TryParse(value, NumberStyles.HexNumber, _culture, out result);
217220

218221
/// <summary>
219222
/// Attempts to parse an integer value.
@@ -224,7 +227,8 @@ protected static bool TryParseDateTimeOffsetCore(string? value, out DateTimeOffs
224227
/// <paramref name="result"/> contains a valid <see cref="int"/> or null when the method returns false.
225228
/// </param>
226229
/// <returns>True if the parse operation was successful; otherwise, false.</returns>
227-
protected static bool TryParseIntCore(string? value, out int result) => int.TryParse(value, NumberStyles.Integer, _culture, out result);
230+
protected static bool TryParseIntCore(string? value, out int result)
231+
=> int.TryParse(value, NumberStyles.Integer, _culture, out result);
228232

229233
/// <summary>
230234
/// Attempts to parse a long value.
@@ -235,7 +239,8 @@ protected static bool TryParseDateTimeOffsetCore(string? value, out DateTimeOffs
235239
/// <paramref name="result"/> contains a valid <see cref="long"/> or null when the method returns false.
236240
/// </param>
237241
/// <returns>True if the parse operation was successful; otherwise, false.</returns>
238-
protected static bool TryParseLongCore(string? value, out long result) => long.TryParse(value, NumberStyles.Integer, _culture, out result);
242+
protected static bool TryParseLongCore(string? value, out long result)
243+
=> long.TryParse(value, NumberStyles.Integer, _culture, out result);
239244

240245
/// <summary>
241246
/// Attempts to parse a decimal value.
@@ -246,7 +251,8 @@ protected static bool TryParseDateTimeOffsetCore(string? value, out DateTimeOffs
246251
/// <paramref name="result"/> contains a valid <see cref="decimal"/> or null when the method returns false.
247252
/// </param>
248253
/// <returns>True if the parse operation was successful; otherwise, false.</returns>
249-
protected static bool TryParseDecimalCore(string? value, out decimal result) => decimal.TryParse(value, NumberStyles.AllowDecimalPoint, _culture, out result);
254+
protected static bool TryParseDecimalCore(string? value, out decimal result)
255+
=> decimal.TryParse(value, NumberStyles.AllowDecimalPoint, _culture, out result);
250256

251257
/// <summary>
252258
/// Attempts to parse an enum value.
@@ -258,7 +264,8 @@ protected static bool TryParseDateTimeOffsetCore(string? value, out DateTimeOffs
258264
/// <paramref name="result"/> contains a valid enum value or null when the method returns false.
259265
/// </param>
260266
/// <returns>True if the parse operation was successful; otherwise, false.</returns>
261-
protected static bool TryParseEnumCore<T>(string? value, out T result) where T : struct => Enum.TryParse<T>(value, out result);
267+
protected static bool TryParseEnumCore<T>(string? value, out T result)
268+
where T : struct => Enum.TryParse<T>(value, out result);
262269

263270
/// <summary>
264271
/// Parses a timestamp in yyMMddHHmmss to a <see cref="DateTimeOffset"/>.
@@ -329,7 +336,8 @@ protected static bool TryParseDateTimeOffsetCore(string? value, out DateTimeOffs
329336
/// The <see cref="OBISDescriptor"/> specifying the value to find in the telegram and parse as <see cref="UnitValue{T}"/>
330337
/// </param>
331338
/// <returns>Returns a <see cref="UnitValue{T}"/> when parsing the given <paramref name="descriptor"/> succeeded, null otherwise.</returns>
332-
protected UnitValue<int>? ParseIntUnit(OBISDescriptor descriptor) => ParseIntUnit(descriptor, GetByDescriptor(descriptor));
339+
protected UnitValue<int>? ParseIntUnit(OBISDescriptor descriptor)
340+
=> ParseIntUnit(descriptor, GetByDescriptor(descriptor));
333341
/// <summary>
334342
/// Attempts to parse a given value with unit from a given <see cref="OBISDescriptor"/>.
335343
/// </summary>
@@ -406,7 +414,8 @@ protected static bool TryParseDateTimeOffsetCore(string? value, out DateTimeOffs
406414
/// <param name="expected">The expected <see cref="OBISUnit"/> the value should represent.</param>
407415
/// <param name="actual">The actual unit the value represents.</param>
408416
/// <returns>True when the given string matches the given <see cref="OBISUnit"/>, false otherwise.</returns>
409-
protected static bool IsCorrectUnit(OBISUnit expected, string? actual) => TryParseEnumCore<OBISUnit>(actual, out var result) && expected == result;
417+
protected static bool IsCorrectUnit(OBISUnit expected, string? actual)
418+
=> TryParseEnumCore<OBISUnit>(actual, out var result) && expected == result;
410419

411420
/// <summary>
412421
/// Parses values in (date)(value)(date)(value)... format to an <see cref="TimeStampedValue{T}"/> enumerable.
@@ -462,7 +471,9 @@ protected static (string? value, string? unit) SplitValues(string? value, char u
462471
/// </summary>
463472
/// <param name="value">The "hex byte format string".</param>
464473
/// <returns>The string the "hex byte string" represents.</returns>
465-
protected static string? DecodeString(string? value) => (!string.IsNullOrEmpty(value) && value.Length % 2 == 0) ? Encoding.ASCII.GetString([.. DecodeHexString(value)]) : value;
474+
protected static string? DecodeString(string? value)
475+
=> (!string.IsNullOrEmpty(value) && value.Length % 2 == 0) ? Encoding.ASCII.GetString([.. DecodeHexString(value)]) : value;
476+
466477
/// <summary>
467478
/// Decodes a string in "hex byte format" to it's byte values.
468479
/// </summary>

DSMRParser/Models/TimeStampedValue.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Globalization;
23

34
namespace DSMRParser.Models;
45

@@ -32,5 +33,65 @@ public TimeStampedValue(DateTimeOffset? dateTimeOffset, T? value)
3233
/// Converts a <see cref="TimeStampedValue{T}"/> to a string.
3334
/// </summary>
3435
/// <returns>Returns a string representing a <see cref="TimeStampedValue{T}"/>.</returns>
35-
public override string ToString() => $"{DateTime:O}: {Value}";
36+
public override string ToString()
37+
=> ToString(null, null, null);
38+
39+
/// <summary>
40+
/// Converts a <see cref="TimeStampedValue{T}"/> to a string.
41+
/// </summary>
42+
/// <param name="valueFormat">The format to use when formatting the value -or- a null
43+
/// reference to use the default format defined for the type of the <see cref="IFormattable"/>
44+
/// implementation.</param>
45+
/// <param name="formatProvider">The provider to use to format the value.</param>
46+
/// <returns>Returns a string representing a <see cref="TimeStampedValue{T}"/>.</returns>
47+
public string ToString(string? valueFormat, IFormatProvider? formatProvider)
48+
=> ToString(valueFormat, null, formatProvider);
49+
50+
/// <summary>
51+
/// Converts a <see cref="TimeStampedValue{T}"/> to a string.
52+
/// </summary>
53+
/// <param name="formatProvider">The provider to use to format the value.</param>
54+
/// <returns>Returns a string representing a <see cref="TimeStampedValue{T}"/>.</returns>
55+
public string ToString(IFormatProvider? formatProvider)
56+
=> ToString(null, null, formatProvider);
57+
58+
/// <summary>
59+
/// Converts a <see cref="TimeStampedValue{T}"/> to a string.
60+
/// </summary>
61+
/// <param name="valueFormat">The format to use when formatting the value -or- a null
62+
/// reference to use the default format defined for the type of the <see cref="IFormattable"/>
63+
/// implementation.</param>
64+
/// <param name="dateFormat">The format to use when formatting the datetime -or- a null
65+
/// reference to use the default format defined for the type of the <see cref="IFormattable"/>
66+
/// implementation.</param>
67+
/// <param name="formatProvider">The provider to use to format the value.</param>
68+
/// <returns>Returns a string representing a <see cref="TimeStampedValue{T}"/>.</returns>
69+
public string ToString(string? valueFormat, string? dateFormat, IFormatProvider? formatProvider)
70+
{
71+
var date = GetFormattedDate(dateFormat, formatProvider);
72+
var value = GetFormattedValue(valueFormat, formatProvider);
73+
74+
return !string.IsNullOrEmpty(date) && !string.IsNullOrEmpty(value)
75+
? $"{date}: {value}"
76+
: !string.IsNullOrEmpty(date)
77+
? date
78+
: !string.IsNullOrEmpty(value)
79+
? value : string.Empty;
80+
}
81+
82+
private string GetFormattedDate(string? dateFormat, IFormatProvider? formatProvider)
83+
=> DateTime switch
84+
{
85+
null => string.Empty,
86+
IFormattable formattable => formattable.ToString(dateFormat ?? "O", formatProvider ?? CultureInfo.InvariantCulture)
87+
};
88+
89+
private string GetFormattedValue(string? valueFormat, IFormatProvider? formatProvider)
90+
=> Value switch
91+
{
92+
null => string.Empty,
93+
IUnitValue unitValue => unitValue.ToString(valueFormat, formatProvider),
94+
IFormattable formattable => formattable.ToString(valueFormat, formatProvider ?? CultureInfo.InvariantCulture),
95+
_ => Value.ToString() ?? string.Empty
96+
};
3697
}

DSMRParser/Models/UnitValue.cs

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1-
namespace DSMRParser.Models;
1+
using System;
2+
using System.Globalization;
3+
4+
namespace DSMRParser.Models;
25

36
/// <summary>
47
/// Represents a value in a given unit.
58
/// </summary>
69
/// <typeparam name="T">The type of the value.</typeparam>
7-
public record UnitValue<T>
10+
public record UnitValue<T> : IUnitValue
811
{
912
/// <summary>
1013
/// Gets the value.
@@ -27,8 +30,41 @@ public UnitValue(T? value, OBISUnit unit)
2730
}
2831

2932
/// <summary>
30-
/// Converts a <see cref="TimeStampedValue{T}"/> to a string.
33+
/// Converts a <see cref="UnitValue{T}"/> to a string.
34+
/// </summary>
35+
/// <returns>Returns a string representing a <see cref="UnitValue{T}"/>.</returns>
36+
public override string ToString()
37+
=> ToString(null, null);
38+
39+
/// <summary>
40+
/// Converts a <see cref="UnitValue{T}"/> to a string.
41+
/// </summary>
42+
/// <param name="format">The format to use -or- a null reference to use the default format
43+
/// defined for the type of the <see cref="IFormattable"/> implementation.</param>
44+
/// <returns>Returns a string representing a <see cref="UnitValue{T}"/>.</returns>
45+
public string ToString(string? format)
46+
=> ToString(format, null);
47+
48+
/// <summary>
49+
/// Converts a <see cref="UnitValue{T}"/> to a string.
50+
/// </summary>
51+
/// <param name="formatProvider">The provider to use to format the value.</param>
52+
/// <returns>Returns a string representing a <see cref="UnitValue{T}"/>.</returns>
53+
public string ToString(IFormatProvider formatProvider)
54+
=> ToString(null, formatProvider);
55+
56+
/// <summary>
57+
/// Converts a <see cref="UnitValue{T}"/> to a string.
3158
/// </summary>
32-
/// <returns>Returns a string representing a <see cref="TimeStampedValue{T}"/>.</returns>
33-
public override string ToString() => $"{Value}{Unit}";
34-
}
59+
/// <param name="format">The format to use -or- a null reference to use the default format
60+
/// defined for the type of the <see cref="IFormattable"/> implementation.</param>
61+
/// <param name="formatProvider">The provider to use to format the value.</param>
62+
/// <returns>Returns a string representing a <see cref="UnitValue{T}"/>.</returns>
63+
public string ToString(string? format, IFormatProvider? formatProvider)
64+
=> Value switch
65+
{
66+
null => string.Empty,
67+
IFormattable formattable => formattable.ToString(format, formatProvider ?? CultureInfo.InvariantCulture),
68+
_ => Value.ToString() ?? string.Empty
69+
} + Unit.ToUnitString();
70+
}

0 commit comments

Comments
 (0)