Skip to content

Commit

Permalink
Fixes #1480. Convert DateOnly properties to local timezone (#1481)
Browse files Browse the repository at this point in the history
* Convert DateOnly properties to local timezone for TypeScriptDateTimeType.Date

* adjust parseDateOnly

* fix ArrayItemDate

* fix DateOnly code
  • Loading branch information
Shaddix committed Sep 16, 2022
1 parent 40d8dcf commit d76549a
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ public class DateCodeGenerationTests
'myTimeSpan': { 'type': 'string', 'format': 'time-span' }
}
}";

[Fact]
public async Task When_date_handling_is_string_then_string_property_is_generated_in_class()

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_string_then_string_property_is_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = await JsonSchema.FromJsonAsync(Json);
Expand All @@ -26,7 +28,8 @@ public async Task When_date_handling_is_string_then_string_property_is_generated
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.String
DateTimeType = TypeScriptDateTimeType.String,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand All @@ -36,8 +39,10 @@ public async Task When_date_handling_is_string_then_string_property_is_generated
Assert.Contains("data[\"myDate\"] = this.myDate;", code);
}

[Fact]
public async Task When_date_handling_is_moment_then_moment_property_is_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_moment_then_moment_property_is_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = await JsonSchema.FromJsonAsync(Json);
Expand All @@ -46,7 +51,8 @@ public async Task When_date_handling_is_moment_then_moment_property_is_generated
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.MomentJS
DateTimeType = TypeScriptDateTimeType.MomentJS,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand Down Expand Up @@ -76,8 +82,10 @@ public async Task When_date_handling_is_moment_then_duration_property_is_generat
Assert.Contains("data[\"myTimeSpan\"] = this.myTimeSpan ? this.myTimeSpan.format('d.hh:mm:ss.SS', { trim: false }) : <any>undefined;", code);
}

[Fact]
public async Task When_date_handling_is_luxon_then_datetime_property_is_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_luxon_then_datetime_property_is_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = await JsonSchema.FromJsonAsync(Json);
Expand All @@ -86,7 +94,8 @@ public async Task When_date_handling_is_luxon_then_datetime_property_is_generate
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.Luxon
DateTimeType = TypeScriptDateTimeType.Luxon,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand Down Expand Up @@ -116,8 +125,10 @@ public async Task When_date_handling_is_luxon_then_duration_property_is_generate
Assert.Contains("data[\"myTimeSpan\"] = this.myTimeSpan ? this.myTimeSpan.toString() : <any>undefined;", code);
}

[Fact]
public async Task When_date_handling_is_dayjs_then_dayjs_property_is_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_dayjs_then_dayjs_property_is_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = await JsonSchema.FromJsonAsync(Json);
Expand All @@ -126,7 +137,8 @@ public async Task When_date_handling_is_dayjs_then_dayjs_property_is_generated_i
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.DayJS
DateTimeType = TypeScriptDateTimeType.DayJS,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand Down Expand Up @@ -156,9 +168,33 @@ public async Task When_date_handling_is_date_then_date_property_is_generated_in_
Assert.Contains("data[\"myDate\"] = this.myDate ? formatDate(this.myDate) : <any>undefined;", code);
Assert.Contains("function formatDate(", code);
}

[Fact]
public async Task When_date_handling_is_offset_moment_then_date_property_is_generated_in_class()
public async Task When_date_handling_is_date_then_date_property_is_generated_in_class_with_local_timezone_conversion()
{
//// Arrange
var schema = await JsonSchema.FromJsonAsync(Json);

//// Act
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
//DateTimeType = TypeScriptDateTimeType.Date,
ConvertDateToLocalTimezone = true
});
var code = generator.GenerateFile("MyClass");

//// Assert
Assert.Contains("myDate: Date", code);
Assert.Contains("this.myDate = _data[\"myDate\"] ? parseDateOnly(_data[\"myDate\"].toString()) : <any>undefined;", code);
Assert.Contains("data[\"myDate\"] = this.myDate ? formatDate(this.myDate) : <any>undefined;", code);
Assert.Contains("function formatDate(", code);
}

[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_offset_moment_then_date_property_is_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = await JsonSchema.FromJsonAsync(Json);
Expand All @@ -167,7 +203,8 @@ public async Task When_date_handling_is_offset_moment_then_date_property_is_gene
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.OffsetMomentJS
DateTimeType = TypeScriptDateTimeType.OffsetMomentJS,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ public class ClassWithDateTimeProperty
public DateTime MyDateTime { get; set; }
}

[Fact]
public async Task When_date_handling_is_string_then_string_property_are_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_string_then_string_property_are_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = JsonSchema.FromType<ClassWithDateTimeProperty>();
Expand All @@ -22,7 +24,8 @@ public async Task When_date_handling_is_string_then_string_property_are_generate
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.String
DateTimeType = TypeScriptDateTimeType.String,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand All @@ -32,8 +35,10 @@ public async Task When_date_handling_is_string_then_string_property_are_generate
Assert.Contains("data[\"MyDateTime\"] = this.myDateTime;", code);
}

[Fact]
public async Task When_date_handling_is_moment_then_moment_property_are_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_moment_then_moment_property_are_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = JsonSchema.FromType<ClassWithDateTimeProperty>();
Expand All @@ -42,7 +47,8 @@ public async Task When_date_handling_is_moment_then_moment_property_are_generate
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.MomentJS
DateTimeType = TypeScriptDateTimeType.MomentJS,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand All @@ -52,8 +58,10 @@ public async Task When_date_handling_is_moment_then_moment_property_are_generate
Assert.Contains("data[\"MyDateTime\"] = this.myDateTime ? this.myDateTime.toISOString() : <any>undefined;", code);
}

[Fact]
public async Task When_date_handling_is_offset_moment_then_moment_property_are_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_offset_moment_then_moment_property_are_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = JsonSchema.FromType<ClassWithDateTimeProperty>();
Expand All @@ -62,7 +70,8 @@ public async Task When_date_handling_is_offset_moment_then_moment_property_are_g
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.OffsetMomentJS
DateTimeType = TypeScriptDateTimeType.OffsetMomentJS,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand All @@ -72,8 +81,10 @@ public async Task When_date_handling_is_offset_moment_then_moment_property_are_g
Assert.Contains("data[\"MyDateTime\"] = this.myDateTime ? this.myDateTime.toISOString(true) : <any>undefined;", code);
}

[Fact]
public async Task When_date_handling_is_dayjs_then_dayjs_property_are_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_dayjs_then_dayjs_property_are_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = JsonSchema.FromType<ClassWithDateTimeProperty>();
Expand All @@ -82,7 +93,8 @@ public async Task When_date_handling_is_dayjs_then_dayjs_property_are_generated_
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
DateTimeType = TypeScriptDateTimeType.DayJS
DateTimeType = TypeScriptDateTimeType.DayJS,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand All @@ -92,8 +104,10 @@ public async Task When_date_handling_is_dayjs_then_dayjs_property_are_generated_
Assert.Contains("data[\"MyDateTime\"] = this.myDateTime ? this.myDateTime.toISOString() : <any>undefined;", code);
}

[Fact]
public async Task When_date_handling_is_date_then_date_property_are_generated_in_class()
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task When_date_handling_is_date_then_date_property_are_generated_in_class(bool convertDateToLocalTimezone)
{
//// Arrange
var schema = JsonSchema.FromType<ClassWithDateTimeProperty>();
Expand All @@ -102,7 +116,8 @@ public async Task When_date_handling_is_date_then_date_property_are_generated_in
var generator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings
{
TypeStyle = TypeScriptTypeStyle.Class,
//DateTimeType = TypeScriptDateTimeType.Date
//DateTimeType = TypeScriptDateTimeType.Date,
ConvertDateToLocalTimezone = convertDateToLocalTimezone
});
var code = generator.GenerateFile("MyClass");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ private static object CreateModel(DataConversionParameters parameters)
//StringToDateCode is used for date and date-time formats
UseJsDate = parameters.Settings.DateTimeType == TypeScriptDateTimeType.Date,
StringToDateCode = GetStringToDateTime(parameters, typeSchema),
StringToDateOnlyCode = parameters.Settings.DateTimeType == TypeScriptDateTimeType.Date
&& parameters.Settings.ConvertDateToLocalTimezone
? "parseDateOnly"
: GetStringToDateTime(parameters, typeSchema),
DateToStringCode = GetDateToString(parameters, typeSchema),
DateTimeToStringCode = GetDateTimeToString(parameters, typeSchema),

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<EmbeddedResource Include="Templates\Interface.liquid" />
<EmbeddedResource Include="Templates\KnockoutClass.liquid" />
<EmbeddedResource Include="Templates\File.ReferenceHandling.liquid" />
<EmbeddedResource Include="Templates\File.ParseDateOnly.liquid" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="9.0.1" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ if (Array.isArray({{ Value }})) {
{% if IsArrayItemNewableObject -%}
{{ Variable }}{% if RequiresStrictPropertyInitialization %}!{% endif %}.push({{ ArrayItemType }}.fromJS(item{% if HandleReferences %}, _mappings{% endif %}));
{% else -%}
{% if IsArrayItemDate or IsArrayItemDateTime -%}
{% if IsArrayItemDate -%}
{{ Variable }}{% if RequiresStrictPropertyInitialization %}!{% endif %}.push({{ StringToDateOnlyCode }}(item));
{% elsif IsArrayItemDateTime -%}
{{ Variable }}{% if RequiresStrictPropertyInitialization %}!{% endif %}.push({{ StringToDateCode }}(item));
{% else -%}
{{ Variable }}{% if RequiresStrictPropertyInitialization %}!{% endif %}.push(item);
Expand All @@ -31,7 +33,9 @@ if ({{ Value }}) {
(<any>{{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] ? {{ DictionaryValueType }}.fromJS({{ Value }}[key]{% if HandleReferences %}, _mappings{% endif %}) : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}<any>{{ NullValue }}{% endif %};
{% elsif IsDictionaryValueNewableArray -%}
(<any>{{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] ? {{ Value }}[key].map((i: any) => {{ DictionaryValueArrayItemType }}.fromJS(i{% if HandleReferences %}, _mappings{% endif %})) : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}<any>{{ NullValue }}{% endif %};
{% elsif IsDictionaryValueDate or IsDictionaryValueDateTime -%}
{% elsif IsDictionaryValueDate -%}
(<any>{{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] ? {{ StringToDateOnlyCode }}({{ Value }}[key].toString()) : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}<any>{{ NullValue }}{% endif %};
{% elsif IsDictionaryValueDateTime -%}
(<any>{{ Variable }}){% if RequiresStrictPropertyInitialization %}!{% endif %}[key] = {{ Value }}[key] ? {{ StringToDateCode }}({{ Value }}[key].toString()) : {% if HasDictionaryValueDefaultValue %}{{ DictionaryValueDefaultValue }}{% else %}<any>{{ NullValue }}{% endif %};
{% else -%}
{% if HasDictionaryValueDefaultValue or NullValue != "undefined" -%}
Expand All @@ -47,7 +51,9 @@ if ({{ Value }}) {
}
{% endif -%}
{% else -%}
{% if IsDate or IsDateTime -%}
{% if IsDate -%}
{{ Variable }} = {{ Value }} ? {{ StringToDateOnlyCode }}({{ Value }}.toString()) : {% if HasDefaultValue %}{{ StringToDateOnlyCode }}({{ DefaultValue }}){% else %}<any>{{ NullValue }}{% endif %};
{% elsif IsDateTime -%}
{{ Variable }} = {{ Value }} ? {{ StringToDateCode }}({{ Value }}.toString()) : {% if HasDefaultValue %}{{ StringToDateCode }}({{ DefaultValue }}){% else %}<any>{{ NullValue }}{% endif %};
{% else -%}
{% if HasDefaultValue or NullValue != "undefined" -%}
Expand All @@ -56,4 +62,4 @@ if ({{ Value }}) {
{{ Variable }} = {{ Value }};
{% endif -%}
{% endif -%}
{% endif -%}
{% endif -%}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function parseDateOnly(s: string) {
const date = new Date(s);
return new Date(date.getTime() +
date.getTimezoneOffset() * 60000);
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ public IEnumerable<CodeArtifact> GenerateTypes(TypeScriptExtensionCode extension
var template = Settings.TemplateFactory.CreateTemplate("TypeScript", "File.FormatDate", new object());
yield return new CodeArtifact("formatDate", CodeArtifactType.Function, CodeArtifactLanguage.CSharp, CodeArtifactCategory.Utility, template);
}

if (artifacts.Any(r => r.Code.Contains("parseDateOnly(")))
{
var template = Settings.TemplateFactory.CreateTemplate("TypeScript", "File.ParseDateOnly", new object());
yield return new CodeArtifact("parseDateOnly", CodeArtifactType.Function, CodeArtifactLanguage.CSharp, CodeArtifactCategory.Utility, template);
}

if (Settings.HandleReferences)
{
var template = Settings.TemplateFactory.CreateTemplate("TypeScript", "File.ReferenceHandling", new object());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,13 @@ public TypeScriptGeneratorSettings()
/// <summary>Gets or sets the date time type (default: 'Date').</summary>
public TypeScriptDateTimeType DateTimeType { get; set; }

/// <summary>
/// Whether to use UTC (default) or local time zone when deserializing dates 'yyyy-MM-dd' (default: 'false').
/// Only applicable if <see cref="DateTimeType"/> is <see cref="TypeScriptDateTimeType.Date"/>.
/// Other DateTimeTypes use local timezone by default.
/// </summary>
public bool ConvertDateToLocalTimezone { get; set; }

/// <summary>Gets or sets the enum style (default: Enum).</summary>
public TypeScriptEnumStyle EnumStyle { get; set; }

Expand Down

0 comments on commit d76549a

Please sign in to comment.