Skip to content

CSHARP-3268: Investigate Military Time Failures #1687

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 25 additions & 25 deletions src/MongoDB.Bson/IO/JsonReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1373,7 +1373,7 @@ private DateTime ParseJavaScriptDateTimeString(string dateTimeString)
{
// if DateTime.TryParse succeeds we're done, otherwise assume it's an RFC 822 formatted DateTime string
DateTime dateTime;
if (DateTime.TryParse(dateTimeString, out dateTime))
if (!dateTimeString.EndsWith("A") && DateTime.TryParse(dateTimeString, out dateTime))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason DateTime.TryParse returns true for time zone A but who knows what it thinks the A means.

If the string ends in A then we should use our own parsing method.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any chance there might be a collision with some other format ending with "A"?
Also there is not handling for "Z", is it a known abbreviation for UTC?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the existing test data nothing else ends with "A". But you are right, there might be a timezone abbreviation we don't know about that ends in A.

We could use a regular expression:

if (!Regex.Matches(dateString, "[0-9 ]A$") && ...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a test case for Z. I think it is a standard abbreviation for UTC and is handled by DateTime.TryParse.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, just wanted to confirm that Z is for UTC.
Looking at .net code, seems that A is related to Era (AD is also valid).

What worries me is that "Mon, 10 Oct 2011 11:22:33 A" is parsed to a certain value in .NET, and we'll be overriding that. If going with this change we need to convince ourselves that this is the right thing to do.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also how will this "Mon, 10 Oct 2011 11:22:33 AD" get parsed?

{
return dateTime;
}
Expand Down Expand Up @@ -1479,30 +1479,30 @@ private DateTime ParseJavaScriptDateTimeString(string dateTimeString)
case "MDT": offset = TimeSpan.FromHours(-6); break;
case "PST": offset = TimeSpan.FromHours(-8); break;
case "PDT": offset = TimeSpan.FromHours(-7); break;
case "A": offset = TimeSpan.FromHours(-1); break;
case "B": offset = TimeSpan.FromHours(-2); break;
case "C": offset = TimeSpan.FromHours(-3); break;
case "D": offset = TimeSpan.FromHours(-4); break;
case "E": offset = TimeSpan.FromHours(-5); break;
case "F": offset = TimeSpan.FromHours(-6); break;
case "G": offset = TimeSpan.FromHours(-7); break;
case "H": offset = TimeSpan.FromHours(-8); break;
case "I": offset = TimeSpan.FromHours(-9); break;
case "K": offset = TimeSpan.FromHours(-10); break;
case "L": offset = TimeSpan.FromHours(-11); break;
case "M": offset = TimeSpan.FromHours(-12); break;
case "N": offset = TimeSpan.FromHours(1); break;
case "O": offset = TimeSpan.FromHours(2); break;
case "P": offset = TimeSpan.FromHours(3); break;
case "Q": offset = TimeSpan.FromHours(4); break;
case "R": offset = TimeSpan.FromHours(5); break;
case "S": offset = TimeSpan.FromHours(6); break;
case "T": offset = TimeSpan.FromHours(7); break;
case "U": offset = TimeSpan.FromHours(8); break;
case "V": offset = TimeSpan.FromHours(9); break;
case "W": offset = TimeSpan.FromHours(10); break;
case "X": offset = TimeSpan.FromHours(11); break;
case "Y": offset = TimeSpan.FromHours(12); break;
case "A": offset = TimeSpan.FromHours(1); break;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sign of all the offsets for A to Y was backwards.

case "B": offset = TimeSpan.FromHours(2); break;
case "C": offset = TimeSpan.FromHours(3); break;
case "D": offset = TimeSpan.FromHours(4); break;
case "E": offset = TimeSpan.FromHours(5); break;
case "F": offset = TimeSpan.FromHours(6); break;
case "G": offset = TimeSpan.FromHours(7); break;
case "H": offset = TimeSpan.FromHours(8); break;
case "I": offset = TimeSpan.FromHours(9); break;
case "K": offset = TimeSpan.FromHours(10); break;
case "L": offset = TimeSpan.FromHours(11); break;
case "M": offset = TimeSpan.FromHours(12); break;
case "N": offset = TimeSpan.FromHours(-1); break;
case "O": offset = TimeSpan.FromHours(-2); break;
case "P": offset = TimeSpan.FromHours(-3); break;
case "Q": offset = TimeSpan.FromHours(-4); break;
case "R": offset = TimeSpan.FromHours(-5); break;
case "S": offset = TimeSpan.FromHours(-6); break;
case "T": offset = TimeSpan.FromHours(-7); break;
case "U": offset = TimeSpan.FromHours(-8); break;
case "V": offset = TimeSpan.FromHours(-9); break;
case "W": offset = TimeSpan.FromHours(-10); break;
case "X": offset = TimeSpan.FromHours(-11); break;
case "Y": offset = TimeSpan.FromHours(-12); break;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean that we should add a case for "Z" here?

I don't see how we would ever reach here with a "Z" because DateTime.TryParse would have already handled it.

default:
var offsetSign = zone.Substring(0);
var offsetHours = zone.Substring(1, 2);
Expand Down
213 changes: 99 additions & 114 deletions tests/MongoDB.Bson.Tests/Jira/CSharp275Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,125 +22,110 @@ namespace MongoDB.Bson.Tests.Jira
{
public class CSharp275Tests
{
private class Test
{
public string Json;
public string Iso;
public Test(string json, string iso)
{
this.Json = json;
this.Iso = iso;
}
}

private Test[] _tests = new Test[]
{
public static object[][] TestParseDatesData => [
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Convert to supply data to a Theory.

// note: use EST/EDT in all Json values to ensure DateTime.Parse doesn't work
// test with dayOfWeek
new Test("Mon, 10 Oct 2011 11:22:33 EDT", "2011-10-10T11:22:33-04:00"),
new Test("Tue, 11 Oct 2011 11:22:33 EDT", "2011-10-11T11:22:33-04:00"),
new Test("Wed, 12 Oct 2011 11:22:33 EDT", "2011-10-12T11:22:33-04:00"),
new Test("Thu, 13 Oct 2011 11:22:33 EDT", "2011-10-13T11:22:33-04:00"),
new Test("Fri, 14 Oct 2011 11:22:33 EDT", "2011-10-14T11:22:33-04:00"),
new Test("Sat, 15 Oct 2011 11:22:33 EDT", "2011-10-15T11:22:33-04:00"),
new Test("Sun, 16 Oct 2011 11:22:33 EDT", "2011-10-16T11:22:33-04:00"),
// test without dayOfWeek
new Test("10 Oct 2011 11:22:33 EDT", "2011-10-10T11:22:33-04:00"),
new Test("11 Oct 2011 11:22:33 EDT", "2011-10-11T11:22:33-04:00"),
new Test("12 Oct 2011 11:22:33 EDT", "2011-10-12T11:22:33-04:00"),
new Test("13 Oct 2011 11:22:33 EDT", "2011-10-13T11:22:33-04:00"),
new Test("14 Oct 2011 11:22:33 EDT", "2011-10-14T11:22:33-04:00"),
new Test("15 Oct 2011 11:22:33 EDT", "2011-10-15T11:22:33-04:00"),
new Test("16 Oct 2011 11:22:33 EDT", "2011-10-16T11:22:33-04:00"),
// test monthName
new Test("1 Jan 2011 11:22:33 EST", "2011-01-01T11:22:33-05:00"),
new Test("1 Feb 2011 11:22:33 EST", "2011-02-01T11:22:33-05:00"),
new Test("1 Mar 2011 11:22:33 EST", "2011-03-01T11:22:33-05:00"),
new Test("1 Apr 2011 11:22:33 EDT", "2011-04-01T11:22:33-04:00"),
new Test("1 May 2011 11:22:33 EDT", "2011-05-01T11:22:33-04:00"),
new Test("1 Jun 2011 11:22:33 EDT", "2011-06-01T11:22:33-04:00"),
new Test("1 Jul 2011 11:22:33 EDT", "2011-07-01T11:22:33-04:00"),
new Test("1 Aug 2011 11:22:33 EDT", "2011-08-01T11:22:33-04:00"),
new Test("1 Sep 2011 11:22:33 EDT", "2011-09-01T11:22:33-04:00"),
new Test("1 Oct 2011 11:22:33 EDT", "2011-10-01T11:22:33-04:00"),
new Test("1 Nov 2011 11:22:33 EDT", "2011-11-01T11:22:33-04:00"),
new Test("1 Dec 2011 11:22:33 EST", "2011-12-01T11:22:33-05:00"),
// test 2-digit year
new Test("Mon, 1 Jan 01 11:22:33 EST", "2001-01-01T11:22:33-5:00"),
new Test("Mon, 1 Jan 29 11:22:33 EST", "2029-01-01T11:22:33-5:00"),
new Test("Tue, 1 Jan 30 11:22:33 EST", "2030-01-01T11:22:33-5:00"),
new Test("Wed, 1 Jan 31 11:22:33 EST", "2031-01-01T11:22:33-5:00"),
new Test("Thu, 1 Jan 32 11:22:33 EST", "2032-01-01T11:22:33-5:00"),
new Test("Fri, 1 Jan 99 11:22:33 EST", "1999-01-01T11:22:33-5:00"),
// test time zones
new Test("Mon, 10 Oct 2011 11:22:33 UT", "2011-10-10T11:22:33-00:00"),
new Test("Mon, 10 Oct 2011 11:22:33 GMT", "2011-10-10T11:22:33-00:00"),
new Test("Mon, 10 Oct 2011 11:22:33 EST", "2011-10-10T11:22:33-05:00"),
new Test("Mon, 10 Oct 2011 11:22:33 EDT", "2011-10-10T11:22:33-04:00"),
new Test("Mon, 10 Oct 2011 11:22:33 CST", "2011-10-10T11:22:33-06:00"),
new Test("Mon, 10 Oct 2011 11:22:33 CDT", "2011-10-10T11:22:33-05:00"),
new Test("Mon, 10 Oct 2011 11:22:33 MST", "2011-10-10T11:22:33-07:00"),
new Test("Mon, 10 Oct 2011 11:22:33 MDT", "2011-10-10T11:22:33-06:00"),
new Test("Mon, 10 Oct 2011 11:22:33 PST", "2011-10-10T11:22:33-08:00"),
new Test("Mon, 10 Oct 2011 11:22:33 PDT", "2011-10-10T11:22:33-07:00"),
// TODO: Investigate Military Time Failures
// https://jira.mongodb.org/browse/CSHARP-3268
//new Test("Mon, 10 Oct 2011 11:22:33 A", "2011-10-10T11:22:33+01:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 B", "2011-10-10T11:22:33+02:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 C", "2011-10-10T11:22:33+03:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 D", "2011-10-10T11:22:33+04:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 E", "2011-10-10T11:22:33+05:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 F", "2011-10-10T11:22:33+06:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 G", "2011-10-10T11:22:33+07:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 H", "2011-10-10T11:22:33+08:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 I", "2011-10-10T11:22:33+09:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 K", "2011-10-10T11:22:33+10:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 L", "2011-10-10T11:22:33+11:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 M", "2011-10-10T11:22:33+12:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 N", "2011-10-10T11:22:33-01:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 O", "2011-10-10T11:22:33-02:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 P", "2011-10-10T11:22:33-03:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 Q", "2011-10-10T11:22:33-04:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 R", "2011-10-10T11:22:33-05:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 S", "2011-10-10T11:22:33-06:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 T", "2011-10-10T11:22:33-07:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 U", "2011-10-10T11:22:33-08:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 V", "2011-10-10T11:22:33-09:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 W", "2011-10-10T11:22:33-10:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 X", "2011-10-10T11:22:33-11:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 Y", "2011-10-10T11:22:33-12:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 Z", "2011-10-10T11:22:33-00:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 +0000", "2011-10-10T11:22:33+00:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 -0000", "2011-10-10T11:22:33-00:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 +0100", "2011-10-10T11:22:33+01:00"),
//new Test("Mon, 10 Oct 2011 11:22:33 -0100", "2011-10-10T11:22:33-01:00")
};
["Mon, 10 Oct 2011 11:22:33 EDT", "2011-10-10T11:22:33-04:00"],
["Tue, 11 Oct 2011 11:22:33 EDT", "2011-10-11T11:22:33-04:00"],
["Wed, 12 Oct 2011 11:22:33 EDT", "2011-10-12T11:22:33-04:00"],
["Thu, 13 Oct 2011 11:22:33 EDT", "2011-10-13T11:22:33-04:00"],
["Fri, 14 Oct 2011 11:22:33 EDT", "2011-10-14T11:22:33-04:00"],
["Sat, 15 Oct 2011 11:22:33 EDT", "2011-10-15T11:22:33-04:00"],
["Sun, 16 Oct 2011 11:22:33 EDT", "2011-10-16T11:22:33-04:00"],
// // test without dayOfWeek
["10 Oct 2011 11:22:33 EDT", "2011-10-10T11:22:33-04:00"],
["11 Oct 2011 11:22:33 EDT", "2011-10-11T11:22:33-04:00"],
["12 Oct 2011 11:22:33 EDT", "2011-10-12T11:22:33-04:00"],
["13 Oct 2011 11:22:33 EDT", "2011-10-13T11:22:33-04:00"],
["14 Oct 2011 11:22:33 EDT", "2011-10-14T11:22:33-04:00"],
["15 Oct 2011 11:22:33 EDT", "2011-10-15T11:22:33-04:00"],
["16 Oct 2011 11:22:33 EDT", "2011-10-16T11:22:33-04:00"],
// // test monthName
["1 Jan 2011 11:22:33 EST", "2011-01-01T11:22:33-05:00"],
["1 Feb 2011 11:22:33 EST", "2011-02-01T11:22:33-05:00"],
["1 Mar 2011 11:22:33 EST", "2011-03-01T11:22:33-05:00"],
["1 Apr 2011 11:22:33 EDT", "2011-04-01T11:22:33-04:00"],
["1 May 2011 11:22:33 EDT", "2011-05-01T11:22:33-04:00"],
["1 Jun 2011 11:22:33 EDT", "2011-06-01T11:22:33-04:00"],
["1 Jul 2011 11:22:33 EDT", "2011-07-01T11:22:33-04:00"],
["1 Aug 2011 11:22:33 EDT", "2011-08-01T11:22:33-04:00"],
["1 Sep 2011 11:22:33 EDT", "2011-09-01T11:22:33-04:00"],
["1 Oct 2011 11:22:33 EDT", "2011-10-01T11:22:33-04:00"],
["1 Nov 2011 11:22:33 EDT", "2011-11-01T11:22:33-04:00"],
["1 Dec 2011 11:22:33 EST", "2011-12-01T11:22:33-05:00"],
// // test 2-digit year
["Mon, 1 Jan 01 11:22:33 EST", "2001-01-01T11:22:33-5:00"],
["Mon, 1 Jan 29 11:22:33 EST", "2029-01-01T11:22:33-5:00"],
["Tue, 1 Jan 30 11:22:33 EST", "2030-01-01T11:22:33-5:00"],
["Wed, 1 Jan 31 11:22:33 EST", "2031-01-01T11:22:33-5:00"],
["Thu, 1 Jan 32 11:22:33 EST", "2032-01-01T11:22:33-5:00"],
["Fri, 1 Jan 99 11:22:33 EST", "1999-01-01T11:22:33-5:00"],
// // test time zones
["Mon, 10 Oct 2011 11:22:33 UT", "2011-10-10T11:22:33-00:00"],
["Mon, 10 Oct 2011 11:22:33 GMT", "2011-10-10T11:22:33-00:00"],
["Mon, 10 Oct 2011 11:22:33 EST", "2011-10-10T11:22:33-05:00"],
["Mon, 10 Oct 2011 11:22:33 EDT", "2011-10-10T11:22:33-04:00"],
["Mon, 10 Oct 2011 11:22:33 CST", "2011-10-10T11:22:33-06:00"],
["Mon, 10 Oct 2011 11:22:33 CDT", "2011-10-10T11:22:33-05:00"],
["Mon, 10 Oct 2011 11:22:33 MST", "2011-10-10T11:22:33-07:00"],
["Mon, 10 Oct 2011 11:22:33 MDT", "2011-10-10T11:22:33-06:00"],
["Mon, 10 Oct 2011 11:22:33 PST", "2011-10-10T11:22:33-08:00"],
["Mon, 10 Oct 2011 11:22:33 PDT", "2011-10-10T11:22:33-07:00"],
// military time zones
["Mon, 10 Oct 2011 11:22:33 A", "2011-10-10T11:22:33+01:00"],
["Mon, 10 Oct 2011 11:22:33 B", "2011-10-10T11:22:33+02:00"],
["Mon, 10 Oct 2011 11:22:33 C", "2011-10-10T11:22:33+03:00"],
["Mon, 10 Oct 2011 11:22:33 D", "2011-10-10T11:22:33+04:00"],
["Mon, 10 Oct 2011 11:22:33 E", "2011-10-10T11:22:33+05:00"],
["Mon, 10 Oct 2011 11:22:33 F", "2011-10-10T11:22:33+06:00"],
["Mon, 10 Oct 2011 11:22:33 G", "2011-10-10T11:22:33+07:00"],
["Mon, 10 Oct 2011 11:22:33 H", "2011-10-10T11:22:33+08:00"],
["Mon, 10 Oct 2011 11:22:33 I", "2011-10-10T11:22:33+09:00"],
["Mon, 10 Oct 2011 11:22:33 K", "2011-10-10T11:22:33+10:00"],
["Mon, 10 Oct 2011 11:22:33 L", "2011-10-10T11:22:33+11:00"],
["Mon, 10 Oct 2011 11:22:33 M", "2011-10-10T11:22:33+12:00"],
["Mon, 10 Oct 2011 11:22:33 N", "2011-10-10T11:22:33-01:00"],
["Mon, 10 Oct 2011 11:22:33 O", "2011-10-10T11:22:33-02:00"],
["Mon, 10 Oct 2011 11:22:33 P", "2011-10-10T11:22:33-03:00"],
["Mon, 10 Oct 2011 11:22:33 Q", "2011-10-10T11:22:33-04:00"],
["Mon, 10 Oct 2011 11:22:33 R", "2011-10-10T11:22:33-05:00"],
["Mon, 10 Oct 2011 11:22:33 S", "2011-10-10T11:22:33-06:00"],
["Mon, 10 Oct 2011 11:22:33 T", "2011-10-10T11:22:33-07:00"],
["Mon, 10 Oct 2011 11:22:33 U", "2011-10-10T11:22:33-08:00"],
["Mon, 10 Oct 2011 11:22:33 V", "2011-10-10T11:22:33-09:00"],
["Mon, 10 Oct 2011 11:22:33 W", "2011-10-10T11:22:33-10:00"],
["Mon, 10 Oct 2011 11:22:33 X", "2011-10-10T11:22:33-11:00"],
["Mon, 10 Oct 2011 11:22:33 Y", "2011-10-10T11:22:33-12:00"],
["Mon, 10 Oct 2011 11:22:33 Z", "2011-10-10T11:22:33-00:00"],
["Mon, 10 Oct 2011 11:22:33 +0000", "2011-10-10T11:22:33+00:00"],
["Mon, 10 Oct 2011 11:22:33 -0000", "2011-10-10T11:22:33-00:00"],
["Mon, 10 Oct 2011 11:22:33 +0100", "2011-10-10T11:22:33+01:00"],
["Mon, 10 Oct 2011 11:22:33 -0100", "2011-10-10T11:22:33-01:00"]
];

[Fact]
public void TestParseDates()
[Theory]
[MemberData(nameof(TestParseDatesData))]
public void TestParseDates(string dateString, string isoString)
{
foreach (var test in _tests)
var json = string.Format("{{ date : new Date('{0}') }}", dateString);
BsonDocument document = null;
try
{
document = BsonDocument.Parse(json);
}
catch (Exception ex)
{
var message = string.Format("Error parsing: new Date(\"{0}\"). Message: {1}.", dateString, ex.Message);
throw new AssertionException(message); // note: the test data for 2-digit years needs to be adjusted at the beginning of each year
}
var dateTime = document["date"].ToUniversalTime();
var expected = DateTime.Parse(isoString).ToUniversalTime();
Assert.Equal(DateTimeKind.Utc, dateTime.Kind);
Assert.Equal(DateTimeKind.Utc, expected.Kind);
if (dateTime != expected)
{
var json = string.Format("{{ date : new Date('{0}') }}", test.Json);
BsonDocument document = null;
try
{
document = BsonDocument.Parse(json);
}
catch (Exception ex)
{
var message = string.Format("Error parsing: new Date(\"{0}\"). Message: {1}.", test.Json, ex.Message);
throw new AssertionException(message); // note: the test data for 2-digit years needs to be adjusted at the beginning of each year
}
var dateTime = document["date"].ToUniversalTime();
var expected = DateTime.Parse(test.Iso).ToUniversalTime();
Assert.Equal(DateTimeKind.Utc, dateTime.Kind);
Assert.Equal(DateTimeKind.Utc, expected.Kind);
if (dateTime != expected)
{
var message = string.Format("Parsing new Date(\"{0}\") did not yield expected result {1}.", test.Json, expected.ToString("o"));
throw new AssertionException(message);
}
var message = string.Format("Parsing new Date(\"{0}\") did not yield expected result {1}.", dateString, expected.ToString("o"));
throw new AssertionException(message);
}
}
}
Expand Down