mirror of
https://github.com/SabreTools/SabreTools.Serialization.git
synced 2026-07-21 08:15:12 +00:00
Changes based on a code sweep with unrelated issue
This commit is contained in:
@@ -171,31 +171,6 @@ namespace SabreTools.Serialization.Readers
|
||||
var obj = new PrimaryVolumeDescriptor();
|
||||
|
||||
obj.Type = VolumeDescriptorType.PRIMARY_VOLUME_DESCRIPTOR;
|
||||
return (PrimaryVolumeDescriptor?)ParseBaseVolumeDescriptor(data, sectorLength, obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a SupplementaryVolumeDescriptor
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||
/// <returns>Filled SupplementaryVolumeDescriptor on success, null on error</returns>
|
||||
public static SupplementaryVolumeDescriptor? ParseSupplementaryVolumeDescriptor(Stream data, short sectorLength)
|
||||
{
|
||||
var obj = new SupplementaryVolumeDescriptor();
|
||||
|
||||
obj.Type = VolumeDescriptorType.SUPPLEMENTARY_VOLUME_DESCRIPTOR;
|
||||
return (SupplementaryVolumeDescriptor?)ParseBaseVolumeDescriptor(data, sectorLength, obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a BaseVolumeDescriptor
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||
/// <returns>Filled BaseVolumeDescriptor on success, null on error</returns>
|
||||
public static BaseVolumeDescriptor? ParseBaseVolumeDescriptor(Stream data, short sectorLength, BaseVolumeDescriptor obj)
|
||||
{
|
||||
obj.Identifier = data.ReadBytes(5);
|
||||
|
||||
// Validate Identifier, return null and rewind if invalid
|
||||
@@ -206,36 +181,12 @@ namespace SabreTools.Serialization.Readers
|
||||
}
|
||||
|
||||
obj.Version = data.ReadByteValue();
|
||||
|
||||
// Read the child-specific field
|
||||
if (obj is PrimaryVolumeDescriptor objPVD)
|
||||
objPVD.UnusedByte = data.ReadByteValue();
|
||||
else if (obj is SupplementaryVolumeDescriptor objSVD)
|
||||
objSVD.VolumeFlags = (VolumeFlags)data.ReadByteValue();
|
||||
else
|
||||
{
|
||||
// Rewind and return for unknown descriptor
|
||||
data.SeekIfPossible(-8, SeekOrigin.Current);
|
||||
return null;
|
||||
}
|
||||
|
||||
obj.UnusedByte = data.ReadByteValue();
|
||||
obj.SystemIdentifier = data.ReadBytes(32);
|
||||
obj.VolumeIdentifier = data.ReadBytes(32);
|
||||
obj.Unused8Bytes = data.ReadBytes(8);
|
||||
obj.VolumeSpaceSize = data.ReadInt32BothEndian();
|
||||
|
||||
// Read the child-specific field
|
||||
if (obj is PrimaryVolumeDescriptor objPVD2)
|
||||
objPVD2.Unused32Bytes = data.ReadBytes(32);
|
||||
else if (obj is SupplementaryVolumeDescriptor objSVD2)
|
||||
objSVD2.EscapeSequences = data.ReadBytes(32);
|
||||
else
|
||||
{
|
||||
// Rewind and return for unknown descriptor
|
||||
data.SeekIfPossible(-120, SeekOrigin.Current);
|
||||
return null;
|
||||
}
|
||||
|
||||
obj.Unused32Bytes = data.ReadBytes(32);
|
||||
obj.VolumeSetSize = data.ReadInt16BothEndian();
|
||||
obj.VolumeSequenceNumber = data.ReadInt16BothEndian();
|
||||
obj.LogicalBlockSize = data.ReadInt16BothEndian();
|
||||
@@ -248,6 +199,7 @@ namespace SabreTools.Serialization.Readers
|
||||
var dr = ParseDirectoryRecord(data, true);
|
||||
if (dr == null)
|
||||
return null;
|
||||
|
||||
obj.RootDirectoryRecord = dr;
|
||||
|
||||
obj.VolumeSetIdentifier = data.ReadBytes(128);
|
||||
@@ -270,7 +222,74 @@ namespace SabreTools.Serialization.Readers
|
||||
|
||||
// Skip remainder of the logical sector
|
||||
if (sectorLength > Constants.MinimumSectorSize)
|
||||
data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current);
|
||||
_ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parse a Stream into a SupplementaryVolumeDescriptor
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||
/// <returns>Filled SupplementaryVolumeDescriptor on success, null on error</returns>
|
||||
public static SupplementaryVolumeDescriptor? ParseSupplementaryVolumeDescriptor(Stream data, short sectorLength)
|
||||
{
|
||||
var obj = new SupplementaryVolumeDescriptor();
|
||||
|
||||
obj.Type = VolumeDescriptorType.SUPPLEMENTARY_VOLUME_DESCRIPTOR;
|
||||
obj.Identifier = data.ReadBytes(5);
|
||||
|
||||
// Validate Identifier, return null and rewind if invalid
|
||||
if (!obj.Identifier.EqualsExactly(Constants.StandardIdentifier))
|
||||
{
|
||||
data.SeekIfPossible(-6, SeekOrigin.Current);
|
||||
return null;
|
||||
}
|
||||
|
||||
obj.Version = data.ReadByteValue();
|
||||
obj.VolumeFlags = (VolumeFlags)data.ReadByteValue();
|
||||
obj.SystemIdentifier = data.ReadBytes(32);
|
||||
obj.VolumeIdentifier = data.ReadBytes(32);
|
||||
obj.Unused8Bytes = data.ReadBytes(8);
|
||||
obj.VolumeSpaceSize = data.ReadInt32BothEndian();
|
||||
obj.EscapeSequences = data.ReadBytes(32);
|
||||
obj.VolumeSetSize = data.ReadInt16BothEndian();
|
||||
obj.VolumeSequenceNumber = data.ReadInt16BothEndian();
|
||||
obj.LogicalBlockSize = data.ReadInt16BothEndian();
|
||||
obj.PathTableSize = data.ReadInt32BothEndian();
|
||||
obj.PathTableLocationL = data.ReadInt32LittleEndian();
|
||||
obj.OptionalPathTableLocationL = data.ReadInt32LittleEndian();
|
||||
obj.PathTableLocationM = data.ReadInt32BigEndian();
|
||||
obj.OptionalPathTableLocationM = data.ReadInt32BigEndian();
|
||||
|
||||
var dr = ParseDirectoryRecord(data, true);
|
||||
if (dr == null)
|
||||
return null;
|
||||
|
||||
obj.RootDirectoryRecord = dr;
|
||||
|
||||
obj.VolumeSetIdentifier = data.ReadBytes(128);
|
||||
obj.PublisherIdentifier = data.ReadBytes(128);
|
||||
obj.DataPreparerIdentifier = data.ReadBytes(128);
|
||||
obj.ApplicationIdentifier = data.ReadBytes(128);
|
||||
obj.CopyrightFileIdentifier = data.ReadBytes(37);
|
||||
obj.AbstractFileIdentifier = data.ReadBytes(37);
|
||||
obj.BibliographicFileIdentifier = data.ReadBytes(37);
|
||||
|
||||
obj.VolumeCreationDateTime = ParseDecDateTime(data);
|
||||
obj.VolumeModificationDateTime = ParseDecDateTime(data);
|
||||
obj.VolumeExpirationDateTime = ParseDecDateTime(data);
|
||||
obj.VolumeEffectiveDateTime = ParseDecDateTime(data);
|
||||
|
||||
obj.FileStructureVersion = data.ReadByteValue();
|
||||
obj.ReservedByte = data.ReadByteValue();
|
||||
obj.ApplicationUse = data.ReadBytes(512);
|
||||
obj.Reserved653Bytes = data.ReadBytes(653);
|
||||
|
||||
// Skip remainder of the logical sector
|
||||
if (sectorLength > Constants.MinimumSectorSize)
|
||||
_ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize);
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -305,7 +324,7 @@ namespace SabreTools.Serialization.Readers
|
||||
|
||||
// Skip remainder of the logical sector
|
||||
if (sectorLength > Constants.MinimumSectorSize)
|
||||
data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current);
|
||||
_ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize);
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -335,7 +354,7 @@ namespace SabreTools.Serialization.Readers
|
||||
|
||||
// Skip remainder of the logical sector
|
||||
if (sectorLength > Constants.MinimumSectorSize)
|
||||
data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current);
|
||||
_ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize);
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -366,7 +385,7 @@ namespace SabreTools.Serialization.Readers
|
||||
|
||||
// Skip remainder of the logical sector
|
||||
if (sectorLength > Constants.MinimumSectorSize)
|
||||
data.SeekIfPossible(sectorLength - Constants.MinimumSectorSize, SeekOrigin.Current);
|
||||
_ = data.ReadBytes(sectorLength - Constants.MinimumSectorSize);
|
||||
|
||||
return obj;
|
||||
}
|
||||
@@ -387,13 +406,13 @@ namespace SabreTools.Serialization.Readers
|
||||
var groups = new List<PathTableGroup>();
|
||||
foreach (VolumeDescriptor vd in vdSet)
|
||||
{
|
||||
if (vd is BaseVolumeDescriptor bvd)
|
||||
{
|
||||
// Parse the path table group in the base volume descriptor
|
||||
var pathTableGroups = ParsePathTableGroup(data, sectorLength, bvd);
|
||||
if (pathTableGroups != null && pathTableGroups.Count > 0)
|
||||
groups.AddRange(pathTableGroups);
|
||||
}
|
||||
if (vd is not BaseVolumeDescriptor bvd)
|
||||
continue;
|
||||
|
||||
// Parse the path table group in the base volume descriptor
|
||||
var pathTableGroups = ParsePathTableGroup(data, sectorLength, bvd);
|
||||
if (pathTableGroups != null && pathTableGroups.Count > 0)
|
||||
groups.AddRange(pathTableGroups);
|
||||
}
|
||||
|
||||
// Return error (null) if no valid path table groups were found
|
||||
@@ -427,22 +446,22 @@ namespace SabreTools.Serialization.Readers
|
||||
if (locationL != 0 && ((locationL * blockLength) + sizeL) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationL * blockLength, SeekOrigin.Begin);
|
||||
groupL.PathTableL = ParsePathTable(data, sectorLength, sizeL, true);
|
||||
groupL.PathTableL = ParsePathTable(data, sizeL, true);
|
||||
}
|
||||
if (locationL2 != 0 && ((locationL2 * blockLength) + sizeL) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationL2 * blockLength, SeekOrigin.Begin);
|
||||
groupL.OptionalPathTableL = ParsePathTable(data, sectorLength, sizeL, true);
|
||||
groupL.OptionalPathTableL = ParsePathTable(data, sizeL, true);
|
||||
}
|
||||
if (locationM != 0 && ((locationM * blockLength) + sizeL) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationM * blockLength, SeekOrigin.Begin);
|
||||
groupL.PathTableM = ParsePathTable(data, sectorLength, sizeL, false);
|
||||
groupL.PathTableM = ParsePathTable(data, sizeL, false);
|
||||
}
|
||||
if (locationM2 != 0 && ((locationM2 * blockLength) + sizeL) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationM2 * blockLength, SeekOrigin.Begin);
|
||||
groupL.OptionalPathTableM = ParsePathTable(data, sectorLength, sizeL, false);
|
||||
groupL.OptionalPathTableM = ParsePathTable(data, sizeL, false);
|
||||
}
|
||||
|
||||
// If no valid path tables were found, don't add the table group
|
||||
@@ -458,22 +477,22 @@ namespace SabreTools.Serialization.Readers
|
||||
if (locationL != 0 && ((locationL * blockLength) + sizeB) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationL * blockLength, SeekOrigin.Begin);
|
||||
groupB.PathTableL = ParsePathTable(data, sectorLength, sizeB, true);
|
||||
groupB.PathTableL = ParsePathTable(data, sizeB, true);
|
||||
}
|
||||
if (locationL2 != 0 && ((locationL2 * blockLength) + sizeB) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationL2 * blockLength, SeekOrigin.Begin);
|
||||
groupB.OptionalPathTableL = ParsePathTable(data, sectorLength, sizeB, true);
|
||||
groupB.OptionalPathTableL = ParsePathTable(data, sizeB, true);
|
||||
}
|
||||
if (locationM != 0 && ((locationM * blockLength) + sizeB) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationM * blockLength, SeekOrigin.Begin);
|
||||
groupB.PathTableM = ParsePathTable(data, sectorLength, sizeB, false);
|
||||
groupB.PathTableM = ParsePathTable(data, sizeB, false);
|
||||
}
|
||||
if (locationM2 != 0 && ((locationM2 * blockLength) + sizeB) < data.Length)
|
||||
{
|
||||
data.SeekIfPossible(locationM2 * blockLength, SeekOrigin.Begin);
|
||||
groupB.OptionalPathTableM = ParsePathTable(data, sectorLength, sizeB, false);
|
||||
groupB.OptionalPathTableM = ParsePathTable(data, sizeB, false);
|
||||
}
|
||||
|
||||
// If no valid path tables were found, don't add the table group
|
||||
@@ -487,11 +506,10 @@ namespace SabreTools.Serialization.Readers
|
||||
/// Parse a Stream into an array of path table records
|
||||
/// </summary>
|
||||
/// <param name="data">Stream to parse</param>
|
||||
/// <param name="sectorLength">Number of bytes in a logical sector (usually 2048)</param>
|
||||
/// <param name="tableSize">Size of the path table</param>
|
||||
/// <param name="littleEndian">True if path table is little endian, false if big endian</param>
|
||||
/// <returns>Filled array of path table records on success, null on error</returns>
|
||||
public static PathTableRecord[]? ParsePathTable(Stream data, short sectorLength, int tableSize, bool littleEndian)
|
||||
public static PathTableRecord[]? ParsePathTable(Stream data, int tableSize, bool littleEndian)
|
||||
{
|
||||
var pathTable = new List<PathTableRecord>();
|
||||
|
||||
@@ -515,7 +533,6 @@ namespace SabreTools.Serialization.Readers
|
||||
}
|
||||
|
||||
record.DirectoryIdentifierLength = directoryIdentifierLength;
|
||||
|
||||
record.ExtendedAttributeRecordLength = data.ReadByteValue();
|
||||
|
||||
// Read numerics with correct endianness
|
||||
@@ -563,21 +580,22 @@ namespace SabreTools.Serialization.Readers
|
||||
var directories = new Dictionary<int, DirectoryExtent>();
|
||||
foreach (VolumeDescriptor vd in vdSet)
|
||||
{
|
||||
if (vd is BaseVolumeDescriptor bvd)
|
||||
{
|
||||
// Determine logical block size
|
||||
short blockLength = bvd.GetLogicalBlockSize(sectorLength);
|
||||
if (vd is not BaseVolumeDescriptor bvd)
|
||||
continue;
|
||||
|
||||
// Parse the root directory pointed to from the base volume descriptor
|
||||
var descriptors = ParseDirectory(data, sectorLength, blockLength, bvd.RootDirectoryRecord, false);
|
||||
if (descriptors == null || descriptors.Count == 0)
|
||||
continue;
|
||||
// Merge dictionaries
|
||||
foreach (var kvp in descriptors)
|
||||
{
|
||||
if (!directories.ContainsKey(kvp.Key))
|
||||
directories.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
// Determine logical block size
|
||||
short blockLength = bvd.GetLogicalBlockSize(sectorLength);
|
||||
|
||||
// Parse the root directory pointed to from the base volume descriptor
|
||||
var descriptors = ParseDirectory(data, sectorLength, blockLength, bvd.RootDirectoryRecord, false);
|
||||
if (descriptors == null || descriptors.Count == 0)
|
||||
continue;
|
||||
|
||||
// Merge dictionaries
|
||||
foreach (var kvp in descriptors)
|
||||
{
|
||||
if (!directories.ContainsKey(kvp.Key))
|
||||
directories.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,11 +680,13 @@ namespace SabreTools.Serialization.Readers
|
||||
// Don't traverse to parent or self
|
||||
if (record.FileIdentifier.EqualsExactly(Constants.CurrentDirectory) || record.FileIdentifier.EqualsExactly(Constants.ParentDirectory))
|
||||
continue;
|
||||
|
||||
// Recursively parse child directory
|
||||
int sectorNum = record.ExtentLocation * blocksPerSector;
|
||||
var dir = ParseDirectory(data, sectorLength, blockLength, record, false);
|
||||
if (dir == null)
|
||||
continue;
|
||||
|
||||
// Add new directories to dictionary
|
||||
foreach (var kvp in dir)
|
||||
{
|
||||
@@ -732,8 +752,10 @@ namespace SabreTools.Serialization.Readers
|
||||
|
||||
// Calculate actual size of record
|
||||
int totalBytes = 33 + obj.FileIdentifierLength;
|
||||
|
||||
// Calculate the size of the system use section (remaining allocated bytes)
|
||||
int systemUseLength = obj.DirectoryRecordLength - 33 - obj.FileIdentifierLength;
|
||||
|
||||
// Account for padding field after file identifier
|
||||
if (obj.FileIdentifierLength % 2 == 0)
|
||||
{
|
||||
|
||||
@@ -160,14 +160,10 @@ namespace SabreTools.Serialization.Wrappers
|
||||
builder.AppendLine(vd.AbstractFileIdentifier, " Abstract Identifier");
|
||||
builder.AppendLine(vd.BibliographicFileIdentifier, " Bibliographic Identifier");
|
||||
|
||||
builder.AppendLine(" Volume Creation Date Time:");
|
||||
Print(builder, vd.VolumeCreationDateTime);
|
||||
builder.AppendLine(" Volume Modification Date Time:");
|
||||
Print(builder, vd.VolumeModificationDateTime);
|
||||
builder.AppendLine(" Volume Expiration Date Time:");
|
||||
Print(builder, vd.VolumeExpirationDateTime);
|
||||
builder.AppendLine(" Volume Effective Date Time:");
|
||||
Print(builder, vd.VolumeEffectiveDateTime);
|
||||
builder.AppendLine(Format(vd.VolumeCreationDateTime), " Volume Creation Date Time:");
|
||||
builder.AppendLine(Format(vd.VolumeModificationDateTime), " Volume Modification Date Time:");
|
||||
builder.AppendLine(Format(vd.VolumeExpirationDateTime), " Volume Expiration Date Time:");
|
||||
builder.AppendLine(Format(vd.VolumeEffectiveDateTime), " Volume Effective Date Time:");
|
||||
|
||||
builder.AppendLine(vd.FileStructureVersion, " File Structure Version");
|
||||
|
||||
@@ -445,51 +441,37 @@ namespace SabreTools.Serialization.Wrappers
|
||||
|
||||
#endregion
|
||||
|
||||
private static void Print(StringBuilder builder, DecDateTime? dt)
|
||||
private static string? Format(DecDateTime? dt)
|
||||
{
|
||||
if (dt == null)
|
||||
{
|
||||
builder.AppendLine(" [NULL]");
|
||||
return;
|
||||
}
|
||||
return null;
|
||||
|
||||
if (dt.Year.IsNumericArray())
|
||||
builder.AppendLine(Encoding.ASCII.GetString(dt.Year), " Year");
|
||||
else
|
||||
builder.AppendLine(dt.Year, " Year");
|
||||
string year = dt.Year.IsNumericArray()
|
||||
? Encoding.ASCII.GetString(dt.Year)
|
||||
: BitConverter.ToString(dt.Year).Replace('-', ' ');
|
||||
string month = dt.Month.IsNumericArray()
|
||||
? Encoding.ASCII.GetString(dt.Month)
|
||||
: BitConverter.ToString(dt.Month).Replace('-', ' ');
|
||||
string day = dt.Day.IsNumericArray()
|
||||
? Encoding.ASCII.GetString(dt.Day)
|
||||
: BitConverter.ToString(dt.Day).Replace('-', ' ');
|
||||
|
||||
if (dt.Month.IsNumericArray())
|
||||
builder.AppendLine(Encoding.ASCII.GetString(dt.Month), " Month");
|
||||
else
|
||||
builder.AppendLine(dt.Month, " Month");
|
||||
string hour = dt.Hour.IsNumericArray()
|
||||
? Encoding.ASCII.GetString(dt.Hour)
|
||||
: BitConverter.ToString(dt.Hour).Replace('-', ' ');
|
||||
string minute = dt.Minute.IsNumericArray()
|
||||
? Encoding.ASCII.GetString(dt.Minute)
|
||||
: BitConverter.ToString(dt.Minute).Replace('-', ' ');
|
||||
string second = dt.Second.IsNumericArray()
|
||||
? Encoding.ASCII.GetString(dt.Second)
|
||||
: BitConverter.ToString(dt.Second).Replace('-', ' ');
|
||||
string csecond = dt.Centisecond.IsNumericArray()
|
||||
? Encoding.ASCII.GetString(dt.Centisecond)
|
||||
: BitConverter.ToString(dt.Centisecond).Replace('-', ' ');
|
||||
|
||||
if (dt.Day.IsNumericArray())
|
||||
builder.AppendLine(Encoding.ASCII.GetString(dt.Day), " Day");
|
||||
else
|
||||
builder.AppendLine(dt.Day, " Day");
|
||||
string tz = $"{((dt.TimezoneOffset - 48) * 15 / 60):+0;-0}:{((dt.TimezoneOffset - 48) * 15 % 60 + 60) % 60:00} (0x{dt.TimezoneOffset:X2})";
|
||||
|
||||
if (dt.Hour.IsNumericArray())
|
||||
builder.AppendLine(Encoding.ASCII.GetString(dt.Hour), " Hour");
|
||||
else
|
||||
builder.AppendLine(dt.Hour, " Hour");
|
||||
|
||||
if (dt.Minute.IsNumericArray())
|
||||
builder.AppendLine(Encoding.ASCII.GetString(dt.Minute), " Minute");
|
||||
else
|
||||
builder.AppendLine(dt.Minute, " Minute");
|
||||
if (dt.Second.IsNumericArray())
|
||||
|
||||
builder.AppendLine(Encoding.ASCII.GetString(dt.Second), " Second");
|
||||
else
|
||||
builder.AppendLine(dt.Second, " Second");
|
||||
|
||||
if (dt.Centisecond.IsNumericArray())
|
||||
builder.AppendLine(Encoding.ASCII.GetString(dt.Centisecond), " Centisecond");
|
||||
else
|
||||
builder.AppendLine(dt.Centisecond, " Centisecond");
|
||||
|
||||
string tz = $"{((dt.TimezoneOffset - 48) * 15 / 60):+0;-0}:{((dt.TimezoneOffset - 48) * 15 % 60 + 60) % 60:00} (0x{dt.TimezoneOffset.ToString("X2")})";
|
||||
builder.AppendLine(tz, " Timezone Offset");
|
||||
return $"{year}-{month}-{day} {hour}:{minute}:{second}.{csecond} [{tz}]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user