Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fixed `kiota download` returning exit code 0 (success) when no results are found or multiple ambiguous matches exist. [#7643](https://github.com/microsoft/kiota/pull/7643)
- Fixed incorrect command hints and telemetry in `kiota plugin generate` handler referencing "client" instead of "plugin". [#7642](https://github.com/microsoft/kiota/pull/7642)
- Fixed Ruby `isStream` always evaluating to false in `CodeMethodWriter`, causing stream/binary responses to never use `send_primitive_async`. [#7639](https://github.com/microsoft/kiota/pull/7639)
- Fixed C# codegen emitting invalid initializer expressions for properties with date/time default values (`Time`, `Date`, `DateTimeOffset`, `TimeSpan`); invalid values (e.g. `24:00:00`) are now silently skipped rather than generating uncompilable code. [#6251](https://github.com/microsoft/kiota/issues/6251)

## [1.31.1] - 2026-04-13

Expand Down
35 changes: 35 additions & 0 deletions src/Kiota.Builder/Writers/CSharp/CodeMethodWriter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Kiota.Builder.CodeDOM;
using Kiota.Builder.Extensions;
Expand Down Expand Up @@ -283,6 +284,15 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
{
defaultValue = convertedDefaultValue;
}
else if (propWithDefault.Type is CodeType dateTimePropType &&
DateTimeTypeNames.Contains(dateTimePropType.Name) &&
defaultValue.StartsWith('"') && defaultValue.EndsWith('"'))
{
var expression = GetDateTimeDefaultValueExpression(dateTimePropType.Name, defaultValue.TrimQuotes());
if (string.IsNullOrEmpty(expression))
continue;
defaultValue = expression;
}
else if (defaultValue.StartsWith('"') && defaultValue.EndsWith('"'))
{
defaultValue = defaultValue.SanitizeQuotedStringLiteral();
Expand All @@ -309,6 +319,31 @@ private void WriteConstructorBody(CodeClass parentClass, CodeMethod currentMetho
}

private const string NullValueString = "null";
private static readonly HashSet<string> DateTimeTypeNames = new(StringComparer.OrdinalIgnoreCase)
{
"Date", "Time", "DateTimeOffset", "TimeSpan"
};
private static string GetDateTimeDefaultValueExpression(string typeName, string rawValue)
{
if (typeName.Equals("Time", StringComparison.OrdinalIgnoreCase) &&
TimeOnly.TryParse(rawValue, CultureInfo.InvariantCulture, out var timeValue))
return $"new Time({timeValue.Hour}, {timeValue.Minute}, {timeValue.Second})";

if (typeName.Equals("Date", StringComparison.OrdinalIgnoreCase) &&
DateOnly.TryParse(rawValue, CultureInfo.InvariantCulture, out var dateValue))
return $"new Date({dateValue.Year}, {dateValue.Month}, {dateValue.Day})";

var escapedRawValue = rawValue.Replace("\"", "\\\"", StringComparison.Ordinal);
if (typeName.Equals("DateTimeOffset", StringComparison.OrdinalIgnoreCase) &&
DateTimeOffset.TryParse(rawValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind | DateTimeStyles.AssumeUniversal, out _))
return $"DateTimeOffset.Parse(\"{escapedRawValue}\", null, global::System.Globalization.DateTimeStyles.RoundtripKind)";
Comment on lines +336 to +339

if (typeName.Equals("TimeSpan", StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(rawValue))
return $"global::System.Xml.XmlConvert.ToTimeSpan(\"{escapedRawValue}\")";
Comment on lines +336 to +343

Comment on lines +341 to +344
return string.Empty;
}
private string DefaultDeserializerValue => $"new Dictionary<string, Action<{conventions.ParseNodeInterfaceName}>>";
private void WriteDeserializerBody(bool shouldHide, CodeMethod codeElement, CodeClass parentClass, LanguageWriter writer)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2053,6 +2053,58 @@ public void WritesConstructor()
Assert.Contains($"{escapedQuotesPropName.ToFirstCharacterUpperCase()} = {expectedValueEscapedQuotes}", result);
}
[Fact]
public void WritesConstructorWithDateTimeTypeDefaults()
{
setup();
method.Kind = CodeMethodKind.Constructor;

parentClass.AddProperty(new CodeProperty
{
Name = "startTime",
DefaultValue = "\"13:00:00\"",
Kind = CodePropertyKind.Custom,
Type = new CodeType { Name = "Time" },
});
parentClass.AddProperty(new CodeProperty
{
Name = "startDate",
DefaultValue = "\"2023-04-19\"",
Kind = CodePropertyKind.Custom,
Type = new CodeType { Name = "Date" },
});
parentClass.AddProperty(new CodeProperty
{
Name = "createdAt",
DefaultValue = "\"2023-04-19T13:00:00Z\"",
Kind = CodePropertyKind.Custom,
Type = new CodeType { Name = "DateTimeOffset" },
});
parentClass.AddProperty(new CodeProperty
{
Name = "duration",
DefaultValue = "\"PT1H\"",
Kind = CodePropertyKind.Custom,
Type = new CodeType { Name = "TimeSpan" },
});
parentClass.AddProperty(new CodeProperty
{
Name = "invalidTime",
DefaultValue = "\"24:00:00\"",
Kind = CodePropertyKind.Custom,
Type = new CodeType { Name = "Time" },
});

writer.Write(method);
var result = tw.ToString();

Assert.Contains("StartTime = new Time(13, 0, 0)", result);
Assert.Contains("StartDate = new Date(2023, 4, 19)", result);
Comment on lines +2100 to +2101
Assert.Contains("CreatedAt = DateTimeOffset.Parse(\"2023-04-19T13:00:00Z\", null, global::System.Globalization.DateTimeStyles.RoundtripKind)", result);
Assert.Contains("Duration = global::System.Xml.XmlConvert.ToTimeSpan(\"PT1H\")", result);
Assert.DoesNotContain("InvalidTime", result);
AssertExtensions.CurlyBracesAreClosed(result, 1);
}
[Fact]
public void WritesWithUrl()
{
setup();
Expand Down
Loading