-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix Tar timestamp conversion from/to string and DateTimeOffset #71038
Changes from all commits
529e4c0
d52a541
33974cd
9973fcd
c8df15b
44789fc
17e6060
9f81016
7fa8776
dc29c6b
c7a0923
cad5599
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -119,20 +119,20 @@ internal static DateTimeOffset GetDateTimeOffsetFromSecondsSinceEpoch(long secon | |
new DateTimeOffset((secondsSinceUnixEpoch * TimeSpan.TicksPerSecond) + DateTime.UnixEpoch.Ticks, TimeSpan.Zero); | ||
|
||
// Converts the specified number of seconds that have passed since the Unix Epoch to a DateTimeOffset. | ||
internal static DateTimeOffset GetDateTimeOffsetFromSecondsSinceEpoch(double secondsSinceUnixEpoch) => | ||
private static DateTimeOffset GetDateTimeOffsetFromSecondsSinceEpoch(decimal secondsSinceUnixEpoch) => | ||
new DateTimeOffset((long)(secondsSinceUnixEpoch * TimeSpan.TicksPerSecond) + DateTime.UnixEpoch.Ticks, TimeSpan.Zero); | ||
|
||
// Converts the specified DateTimeOffset to the number of seconds that have passed since the Unix Epoch. | ||
internal static double GetSecondsSinceEpochFromDateTimeOffset(DateTimeOffset dateTimeOffset) => | ||
((double)(dateTimeOffset.UtcDateTime - DateTime.UnixEpoch).Ticks) / TimeSpan.TicksPerSecond; | ||
private static decimal GetSecondsSinceEpochFromDateTimeOffset(DateTimeOffset dateTimeOffset) => | ||
((decimal)(dateTimeOffset.UtcDateTime - DateTime.UnixEpoch).Ticks) / TimeSpan.TicksPerSecond; | ||
|
||
// If the specified fieldName is found in the provided dictionary and it is a valid double number, returns true and sets the value in 'dateTimeOffset'. | ||
// If the specified fieldName is found in the provided dictionary and it is a valid decimal number, returns true and sets the value in 'dateTimeOffset'. | ||
internal static bool TryGetDateTimeOffsetFromTimestampString(Dictionary<string, string>? dict, string fieldName, out DateTimeOffset dateTimeOffset) | ||
{ | ||
dateTimeOffset = default; | ||
if (dict != null && | ||
dict.TryGetValue(fieldName, out string? value) && | ||
double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out double secondsSinceEpoch)) | ||
decimal.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out decimal secondsSinceEpoch)) | ||
{ | ||
dateTimeOffset = GetDateTimeOffsetFromSecondsSinceEpoch(secondsSinceEpoch); | ||
return true; | ||
|
@@ -143,8 +143,10 @@ internal static bool TryGetDateTimeOffsetFromTimestampString(Dictionary<string, | |
// Converts the specified DateTimeOffset to the string representation of seconds since the Unix Epoch. | ||
internal static string GetTimestampStringFromDateTimeOffset(DateTimeOffset timestamp) | ||
{ | ||
double secondsSinceEpoch = GetSecondsSinceEpochFromDateTimeOffset(timestamp); | ||
return secondsSinceEpoch.ToString("F9", CultureInfo.InvariantCulture); // 6 decimals, no commas | ||
decimal secondsSinceEpoch = GetSecondsSinceEpochFromDateTimeOffset(timestamp); | ||
|
||
// Use 'G' to ensure the decimals get preserved (avoid losing precision). | ||
return secondsSinceEpoch.ToString("G", CultureInfo.InvariantCulture); | ||
} | ||
|
||
// If the specified fieldName is found in the provided dictionary and is a valid string representation of a number, returns true and sets the value in 'baseTenInteger'. | ||
|
@@ -179,6 +181,14 @@ internal static int GetTenBaseNumberFromOctalAsciiChars(Span<byte> buffer) | |
return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt32(str, fromBase: 8); | ||
} | ||
|
||
// Receives a byte array that represents an ASCII string containing a number in octal base. | ||
// Converts the array to an octal base number, then transforms it to ten base and returns it. | ||
internal static long GetTenBaseLongFromOctalAsciiChars(Span<byte> buffer) | ||
{ | ||
string str = GetTrimmedAsciiString(buffer); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we really need to create an intermediate string just to parse it into an integer? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should be able to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see a Utf8Parser API that parses the integer from an "octal" string. I see it supports There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that a new allocation is not needed. This code can be improved to instead of returning a string, it returns an int representing the length of the ROS for slicing. The method checks that the last character(s) in the ROS are either a Do you mind if I address this request later? I'd like to get this PR merged just for the DateTimeOffsets. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think that is fine. |
||
return string.IsNullOrEmpty(str) ? 0 : Convert.ToInt64(str, fromBase: 8); | ||
} | ||
|
||
// Returns the string contained in the specified buffer of bytes, | ||
// in the specified encoding, removing the trailing null or space chars. | ||
private static string GetTrimmedString(ReadOnlySpan<byte> buffer, Encoding encoding) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,14 +76,14 @@ private TarEntry GetFirstEntry(MemoryStream dataStream, TarEntryType entryType, | |
PaxTarEntry paxEntry = firstEntry as PaxTarEntry; | ||
Assert.Contains("atime", paxEntry.ExtendedAttributes); | ||
Assert.Contains("ctime", paxEntry.ExtendedAttributes); | ||
CompareDateTimeOffsets(firstEntry.ModificationTime, GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, "atime")); | ||
CompareDateTimeOffsets(firstEntry.ModificationTime, GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, "ctime")); | ||
Assert.Equal(firstEntry.ModificationTime, GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, "atime")); | ||
Assert.Equal(firstEntry.ModificationTime, GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, "ctime")); | ||
} | ||
else if (format is TarEntryFormat.Gnu) | ||
{ | ||
GnuTarEntry gnuEntry = firstEntry as GnuTarEntry; | ||
CompareDateTimeOffsets(firstEntry.ModificationTime, gnuEntry.AccessTime); | ||
CompareDateTimeOffsets(firstEntry.ModificationTime, gnuEntry.ChangeTime); | ||
Assert.Equal(firstEntry.ModificationTime, gnuEntry.AccessTime); | ||
Assert.Equal(firstEntry.ModificationTime, gnuEntry.ChangeTime); | ||
} | ||
|
||
return firstEntry; | ||
|
@@ -123,18 +123,18 @@ private TarEntry ConvertAndVerifyEntry(TarEntry originalEntry, TarEntryType entr | |
if (formatToConvert is TarEntryFormat.Pax) | ||
{ | ||
PaxTarEntry paxEntry = convertedEntry as PaxTarEntry; | ||
DateTimeOffset actualAccessTime = GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, "atime"); | ||
DateTimeOffset actualChangeTime = GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, "atime"); | ||
DateTimeOffset actualAccessTime = GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, PaxEaATime); | ||
DateTimeOffset actualChangeTime = GetDateTimeOffsetFromTimestampString(paxEntry.ExtendedAttributes, PaxEaCTime); | ||
if (originalEntry.Format is TarEntryFormat.Pax or TarEntryFormat.Gnu) | ||
{ | ||
GetExpectedTimestampsFromOriginalPaxOrGnu(originalEntry, out DateTimeOffset expectedATime, out DateTimeOffset expectedCTime); | ||
CompareDateTimeOffsets(expectedATime, actualAccessTime); | ||
CompareDateTimeOffsets(expectedCTime, actualChangeTime); | ||
Assert.Equal(expectedATime, actualAccessTime); | ||
Assert.Equal(expectedCTime, actualChangeTime); | ||
} | ||
else if (originalEntry.Format is TarEntryFormat.Ustar or TarEntryFormat.V7) | ||
{ | ||
CompareDateTimeOffsets(initialNow, actualAccessTime); | ||
CompareDateTimeOffsets(initialNow, actualChangeTime); | ||
AssertExtensions.GreaterThanOrEqualTo(actualAccessTime, initialNow); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In these cases, the The cases that do an |
||
AssertExtensions.GreaterThanOrEqualTo(actualChangeTime, initialNow); | ||
} | ||
} | ||
|
||
|
@@ -144,13 +144,13 @@ private TarEntry ConvertAndVerifyEntry(TarEntry originalEntry, TarEntryType entr | |
if (originalEntry.Format is TarEntryFormat.Pax or TarEntryFormat.Gnu) | ||
{ | ||
GetExpectedTimestampsFromOriginalPaxOrGnu(originalEntry, out DateTimeOffset expectedATime, out DateTimeOffset expectedCTime); | ||
CompareDateTimeOffsets(expectedATime, gnuEntry.AccessTime); | ||
CompareDateTimeOffsets(expectedCTime, gnuEntry.ChangeTime); | ||
AssertExtensions.GreaterThanOrEqualTo(gnuEntry.AccessTime, expectedATime); | ||
AssertExtensions.GreaterThanOrEqualTo(gnuEntry.ChangeTime, expectedCTime); | ||
} | ||
else if (originalEntry.Format is TarEntryFormat.Ustar or TarEntryFormat.V7) | ||
{ | ||
CompareDateTimeOffsets(initialNow, gnuEntry.AccessTime); | ||
CompareDateTimeOffsets(initialNow, gnuEntry.ChangeTime); | ||
AssertExtensions.GreaterThanOrEqualTo(gnuEntry.AccessTime, initialNow); | ||
AssertExtensions.GreaterThanOrEqualTo(gnuEntry.ChangeTime, initialNow); | ||
} | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to create a test that has a date time in 2039 to make sure we can handle dates past 2038.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added tests for the epochalypse and the max upper limit in octal.