Skip to content

Commit 7921d84

Browse files
committed
Merge branch 'main' into windows-test
2 parents 297c6a2 + 539d50f commit 7921d84

14 files changed

Lines changed: 170 additions & 71 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@
1010
- Tests target .NET Core 6.0-9.0
1111
* config: update SFDC API default to v64.0
1212
* content: Updated default SFDC object models in NetCoreForce.Models to SF API v64.0
13+
* fix: remove forced UTC conversion for DateTimeKind.Unspecified DateTimes.
14+
this caused inconsistent behavior between Windows and other platforms as under Windows it would end up adding a utc offset to the date.
15+
NetCoreForce.Client.Tests.DateFormatTests.FullDateFormatFromDateTime
16+
Expected: "2018-01-01T00:00:00+00:00"
17+
Actual: "2018-01-01T00:00:00-05:00" (under Windows)
1318

1419
### 2025-02-24 v5.0.0-Beta
1520

build.props renamed to Directory.Build.props

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
<RepositoryUrl>https://github.com/anthonyreilly/NetCoreForce</RepositoryUrl>
2020
<PackageReleaseNotes>Release notes and documentation can be found on the project site: https://github.com/anthonyreilly/NetCoreForce</PackageReleaseNotes>
2121

22+
<!-- Intentionally targeting EOL frameworks -->
23+
<CheckEolTargetFramework>false</CheckEolTargetFramework>
24+
2225
<!--Project Root-->
2326
<SolutionDir>$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), *.sln))</SolutionDir>
2427
</PropertyGroup>

docs/getting-started.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
# Getting Started
22

3-
4-
53
## Basic Usage Example
64

75
```csharp
@@ -45,11 +43,21 @@ string caseAccountName = firstCase.Account.Name;
4543
string caseContactName = firstCase.Contact.Name;
4644
```
4745

48-
Nested queries are not fully supported - the subquery results will not be complete if they exceed the batch size as the NextRecordsUrl in the subquery results is not being acted upon. Instead use the relationship syntax in the example above.
46+
While you can use a SOQL query such as
47+
```SELECT * FROM Case```
48+
this will be inefficient as it will retrieve all fields for the object. It is highly recommended to only query those fields you will need, e.g.
49+
```SELECT Id, FirstName, LastName, Email, Account.Id, Account.Name FROM Contact```
50+
51+
[Nested queries](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_relationships_query_using.htm) are not fully supported - the subquery results will not be complete if they exceed the batch size as the NextRecordsUrl in the subquery results is not being acted upon. Instead use the relationship syntax in the examples above.
4952
```
5053
// *NOT* fully supported
51-
"SELECT Id,CaseNumber, (Select Contact.Name from Account) FROM Case"
54+
SELECT Name, (SELECT Email FROM Contacts) FROM Account
55+
// Instead use:
56+
SELECT Email, Account.Name FROM Contact
5257
```
58+
59+
For more on SOQL queries see the Salesforce Documentation: [Salesforce Object Query Language (SOQL)](https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql.htm)
60+
5361
---
5462

5563
## Asynchronous Batch Processing

src/NetCoreForce.Client.Tests/DateFormatTests.cs

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,21 @@ namespace NetCoreForce.Client.Tests
99
//https://developer.salesforce.com/docs/atlas.en-us.soql_sosl.meta/soql_sosl/sforce_api_calls_soql_select_dateformats.htm
1010
public class DateFormatTests
1111
{
12+
1213
public class TestObject
1314
{
1415
public DateTimeOffset DateProp { get; set; }
1516
}
1617

17-
readonly DateTimeOffset _dto = new DateTimeOffset(2017,5,1,12,0,0,0,new TimeSpan(-5,0,0));
18+
readonly DateTimeOffset _dto = new DateTimeOffset(2017, 5, 1, 12, 0, 0, 0, new TimeSpan(-5, 0, 0));
1819
readonly string _expectedDate = "2017-05-01T12:00:00-05:00";
1920

2021
[Fact]
2122
public void SerializedFullDate()
2223
{
2324
TestObject obj = new TestObject() { DateProp = _dto };
2425

25-
string serialized = JsonSerializer.SerializeComplete(obj, false);
26+
string serialized = JsonSerializer.SerializeComplete(obj, false);
2627

2728
Assert.Contains(_expectedDate, serialized);
2829
}
@@ -43,16 +44,65 @@ public void FullDateFormat()
4344
Assert.Equal(_expectedDate, convertedDate);
4445
}
4546

46-
[Fact]
47-
public void FullDateFormatFromDateTime()
47+
[Theory]
48+
[InlineData(DateTimeKind.Utc)]
49+
[InlineData(DateTimeKind.Local)]
50+
[InlineData(DateTimeKind.Unspecified)]
51+
public void FullDateFormat_From_DateTime_Kind(DateTimeKind kind)
4852
{
49-
var dt = new DateTime(2018,1,1,0,0,0, DateTimeKind.Utc);
53+
DateTime dateTimeLocal = new DateTime(2018, 1, 1, 0, 0, 0, kind);
54+
55+
TimeSpan localOffset = TimeZoneInfo.Local.GetUtcOffset(dateTimeLocal);
56+
57+
if(kind == DateTimeKind.Utc)
58+
{
59+
// for UTC DateTime, the offset is always zero
60+
localOffset = TimeSpan.Zero;
61+
}
62+
63+
string convertedDate = DateFormats.FullDateString(dateTimeLocal);
64+
65+
// get timespan formatting to end up with e.g. "2018-01-01T00:00:00-05:00"
66+
string timeSpanFormat = GetTimeSpanFormat(localOffset);
67+
string expectedConvertedDate = $"2018-01-01T00:00:00{localOffset.ToString(timeSpanFormat)}";
68+
69+
Assert.Equal(expectedConvertedDate, convertedDate);
70+
}
71+
72+
private string GetTimeSpanFormat(TimeSpan ts)
73+
{
74+
// get timespan formatting to end up with e.g. "2018-01-01T00:00:00-05:00"
75+
return (ts < TimeSpan.Zero ? "\\-" : "\\+") + "hh\\:mm";
76+
}
77+
78+
[Theory]
79+
[InlineData("America/New_York")]
80+
[InlineData("America/Phoenix")]
81+
[InlineData("Europe/London")]
82+
[InlineData("Asia/Tokyo")]
83+
[InlineData("Asia/Kathmandu")] // Nepal Time (UTC+5:45)
84+
[InlineData("Pacific/Auckland")]
85+
[InlineData("Europe/Moscow")]
86+
[InlineData("Asia/Shanghai")]
87+
public void FullDateFormat_From_Other_Timezone(string timeZoneId)
88+
{
89+
using (new LocalTimeZoneInfoMocker(TimeZoneInfo.FindSystemTimeZoneById(timeZoneId)))
90+
{
91+
DateTime dateTimeUnspecified = new DateTime(2018, 1, 1, 0, 0, 0);
92+
93+
// check that the DateTimeKind is Unspecified
94+
Assert.Equal(DateTimeKind.Unspecified, dateTimeUnspecified.Kind);
95+
96+
TimeSpan localOffset = TimeZoneInfo.Local.GetUtcOffset(dateTimeUnspecified);
5097

51-
string convertedDate = DateFormats.FullDateString(dt);
98+
string convertedDate = DateFormats.FullDateString(dateTimeUnspecified);
5299

53-
string expected = "2018-01-01T00:00:00+00:00";
100+
// get timespan formatting to end up with e.g. "2018-01-01T00:00:00-05:00"
101+
string timeSpanFormat = GetTimeSpanFormat(localOffset);
102+
string expectedConvertedDate = $"2018-01-01T00:00:00{localOffset.ToString(timeSpanFormat)}";
54103

55-
Assert.Equal(expected, convertedDate);
104+
Assert.Equal(expectedConvertedDate, convertedDate);
105+
}
56106
}
57107

58108
[Fact]

src/NetCoreForce.Client.Tests/NetCoreForce.Client.Tests.csproj

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.props))\build.props" />
32
<PropertyGroup>
43
<TargetFrameworks>$(TestTargetFrameworks)</TargetFrameworks>
5-
<LangVersion>$(LangVersion)</LangVersion>
64
<IsPackable>false</IsPackable>
7-
<!-- Ignore warnings about referencing obsolete methods -->
8-
<!-- <NoWarn>CS0618</NoWarn> -->
9-
<CheckEolTargetFramework>false</CheckEolTargetFramework>
105
</PropertyGroup>
116

127
<Choose>
@@ -54,4 +49,4 @@
5449
</None>
5550
</ItemGroup>
5651

57-
</Project>
52+
</Project>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace NetCoreForce.Client.Tests
5+
{
6+
/// <summary>
7+
/// Sample TimeZone IDs for use in tests
8+
/// </summary>
9+
public static class TimeZoneIds
10+
{
11+
public static string TimeZoneIdNewYork = "America/New_York";
12+
public static string TimeZoneIdPhoenix = "America/Phoenix";
13+
public static string TimeZoneIdLondon = "Europe/London";
14+
public static string TimeZoneIdTokyo = "Asia/Tokyo";
15+
}
16+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Reflection;
3+
4+
namespace NetCoreForce.Client.Tests
5+
{
6+
public class LocalTimeZoneInfoMocker : IDisposable
7+
{
8+
private readonly TimeZoneInfo _actualLocalTimeZoneInfo;
9+
10+
public LocalTimeZoneInfoMocker(TimeZoneInfo mockTimeZoneInfo)
11+
{
12+
_actualLocalTimeZoneInfo = TimeZoneInfo.Local;
13+
SetLocalTimeZone(mockTimeZoneInfo);
14+
}
15+
16+
private static void SetLocalTimeZone(TimeZoneInfo timeZoneInfo)
17+
{
18+
var info = typeof(TimeZoneInfo).GetField("s_cachedData", BindingFlags.NonPublic | BindingFlags.Static);
19+
object cachedData = info.GetValue(null);
20+
21+
var field = cachedData.GetType().GetField("_localTimeZone", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Instance);
22+
field.SetValue(cachedData, timeZoneInfo);
23+
}
24+
25+
public void Dispose()
26+
{
27+
TimeZoneInfo.ClearCachedData();
28+
SetLocalTimeZone(_actualLocalTimeZoneInfo);
29+
}
30+
}
31+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using Xunit;
4+
using NetCoreForce.Client;
5+
using NetCoreForce.Client.Models;
6+
7+
namespace NetCoreForce.Client.Tests
8+
{
9+
public class TimeZoneInfoMockerTests
10+
{
11+
[Fact]
12+
public void TestLocalTimeZoneInfoMocker()
13+
{
14+
TimeSpan mockedUtcOffset;
15+
TimeSpan actualLocalUtcOffset = TimeZoneInfo.Local.BaseUtcOffset;
16+
17+
using (new LocalTimeZoneInfoMocker(TimeZoneInfo.FindSystemTimeZoneById(TimeZoneIds.TimeZoneIdPhoenix)))
18+
{
19+
// shifted to Phoenix TZ
20+
Assert.Equal(TimeZoneIds.TimeZoneIdPhoenix, TimeZoneInfo.Local.Id);
21+
mockedUtcOffset = TimeZoneInfo.Local.BaseUtcOffset;
22+
Assert.NotEqual(TimeZoneInfo.Local.BaseUtcOffset, actualLocalUtcOffset);
23+
}
24+
25+
// back to local machine's TZ
26+
Assert.NotEqual(TimeZoneInfo.Local.Id, TimeZoneIds.TimeZoneIdPhoenix);
27+
Assert.NotEqual(TimeZoneInfo.Local.BaseUtcOffset, mockedUtcOffset);
28+
}
29+
30+
}
31+
}

src/NetCoreForce.Client/DateFormats.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public static string FullDateString(DateTimeOffset dto)
5454
/// <returns></returns>
5555
public static string FullDateString(DateTime dt)
5656
{
57-
return dt.ToUniversalTime().ToString(_FullFormat);
57+
return dt.ToString(_FullFormat);
5858
}
5959

6060
public static string FullDateString(DateTime dt, TimeSpan offset)

src/NetCoreForce.Client/NetCoreForce.Client.csproj

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,14 @@
11
<Project Sdk="Microsoft.NET.Sdk">
2-
<Import
3-
Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.props))\build.props" />
42
<PropertyGroup>
53
<AssemblyName>NetCoreForce.Client</AssemblyName>
64
<Product>NetCoreForce.Client</Product>
75
<OutputType>Library</OutputType>
86
<TargetFrameworks>$(LibTargetFrameworks)</TargetFrameworks>
9-
<VersionPrefix>$(VersionPrefix)</VersionPrefix>
10-
<VersionSuffix>$(VersionSuffix)</VersionSuffix>
7+
118
<!--NuGet-->
12-
<Authors>$(Authors)</Authors>
13-
<Copyright>$(Copyright)</Copyright>
149
<Description>A .NET Standard and .NET Core Salesforce REST API integration library</Description>
1510
<IsPackable>true</IsPackable>
1611
<PackageId>NetCoreForce.Client</PackageId>
17-
<PackageLicenseExpression>$(PackageLicenseExpression)</PackageLicenseExpression>
18-
<PackageProjectUrl>$(PackageProjectUrl)</PackageProjectUrl>
19-
<PackageRequireLicenseAcceptance>$(PackageRequireLicenseAcceptance)</PackageRequireLicenseAcceptance>
20-
<PackageTags>$(PackageTags)</PackageTags>
21-
<RepositoryType>$(RepositoryType)</RepositoryType>
22-
<RepositoryUrl>$(RepositoryUrl)</RepositoryUrl>
23-
<PackageReleaseNotes>$(PackageReleaseNotes)</PackageReleaseNotes>
2412
<PackageReadmeFile>README.md</PackageReadmeFile>
2513
</PropertyGroup>
2614

@@ -34,7 +22,6 @@
3422
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
3523
<!-- warning CS1591: Missing XML comment for publicly visible type or member -->
3624
<NoWarn>CS1591</NoWarn>
37-
<CheckEolTargetFramework>false</CheckEolTargetFramework>
3825
</PropertyGroup>
3926

4027
<PropertyGroup Condition="'$(Configuration)'=='Release'">

0 commit comments

Comments
 (0)