Skip to content

Commit

Permalink
Add new parapeter for culture
Browse files Browse the repository at this point in the history
- Added IFormatProvider to specify culture
- Updated private fields not using prefixes to meet coding conventions
  • Loading branch information
justinyoo committed Jan 13, 2017
1 parent 0f224f0 commit 3158fa3
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 19 deletions.
136 changes: 127 additions & 9 deletions YamlDotNet.Test/Serialization/DateTimeConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,15 @@ public void Given_Yaml_WithValidDateTimeFormat_ReadYaml_ShouldReturn_Result(int
/// <remarks>The converter instance uses its default parameter of UTC.</remarks>
[Theory]
[InlineData(2016, 12, 31, "yyyy-MM-dd", "yyyy/MM/dd HH:mm:ss")]
public void Given_Yaml_With_ValidDateTimeFormatAndUnspecified_ReadYaml_ShouldReturn_Result(int year, int month, int day, string format1, string format2)
public void Given_Yaml_WithSpecificCultureAndValidDateTimeFormat_ReadYaml_ShouldReturn_Result(int year, int month, int day, string format1, string format2)
{
var yaml = $"{year}-{month:00}-{day:00}";

var parser = A.Fake<IParser>();
A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(yaml));

var converter = new DateTimeConverter(DateTimeKind.Unspecified, new[] { format1, format2 });
var culture = CultureInfo.GetCultureInfo("ko-KR"); // Sample specific culture
var converter = new DateTimeConverter(provider: culture, formats: new[] { format1, format2 });

var result = converter.ReadYaml(parser, typeof(DateTime));

Expand All @@ -234,14 +235,46 @@ public void Given_Yaml_With_ValidDateTimeFormatAndUnspecified_ReadYaml_ShouldRet
/// <remarks>The converter instance uses its default parameter of UTC.</remarks>
[Theory]
[InlineData(2016, 12, 31, "yyyy-MM-dd", "yyyy/MM/dd HH:mm:ss")]
public void Given_Yaml_With_ValidDateTimeFormatAndLocal_ReadYaml_ShouldReturn_Result(int year, int month, int day, string format1, string format2)
public void Given_Yaml_WithValidDateTimeFormatAndUnspecified_ReadYaml_ShouldReturn_Result(int year, int month, int day, string format1, string format2)
{
var yaml = $"{year}-{month:00}-{day:00}";

var parser = A.Fake<IParser>();
A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(yaml));

var converter = new DateTimeConverter(DateTimeKind.Local, new[] { format1, format2 });
var converter = new DateTimeConverter(DateTimeKind.Unspecified, formats: new[] { format1, format2 });

var result = converter.ReadYaml(parser, typeof(DateTime));

result.Should().BeOfType<DateTime>();
((DateTime)result).Kind.Should().Be(DateTimeKind.Utc);
((DateTime)result).ToUniversalTime().Year.Should().Be(year);
((DateTime)result).ToUniversalTime().Month.Should().Be(month);
((DateTime)result).ToUniversalTime().Day.Should().Be(day);
((DateTime)result).ToUniversalTime().Hour.Should().Be(0);
((DateTime)result).ToUniversalTime().Minute.Should().Be(0);
((DateTime)result).ToUniversalTime().Second.Should().Be(0);
}

/// <summary>
/// Tests whether the ReadYaml() method should return expected result or not.
/// </summary>
/// <param name="year">Year value.</param>
/// <param name="month">Month value.</param>
/// <param name="day">Day value.</param>
/// <param name="format1">Designated date/time format 1.</param>
/// <param name="format2">Designated date/time format 2.</param>
/// <remarks>The converter instance uses its default parameter of UTC.</remarks>
[Theory]
[InlineData(2016, 12, 31, "yyyy-MM-dd", "yyyy/MM/dd HH:mm:ss")]
public void Given_Yaml_WithValidDateTimeFormatAndLocal_ReadYaml_ShouldReturn_Result(int year, int month, int day, string format1, string format2)
{
var yaml = $"{year}-{month:00}-{day:00}";

var parser = A.Fake<IParser>();
A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(yaml));

var converter = new DateTimeConverter(DateTimeKind.Local, formats: new[] { format1, format2 });

var result = converter.ReadYaml(parser, typeof(DateTime));

Expand Down Expand Up @@ -290,6 +323,58 @@ public void Given_Yaml_WithTimeFormat_ReadYaml_ShouldReturn_Result(string format
result.Should().Be(expected);
}

/// <summary>
/// Tests whether the ReadYaml() method should return expected result or not.
/// </summary>
/// <param name="format">Date/Time format.</param>
/// <param name="locale">Locale value.</param>
/// <param name="value">Date/Time value.</param>
/// <remarks>Standard format "F" and "U" cannot be used at the same time as their string representations are identical to each other.</remarks>
[Theory]
[InlineData("d", "fr-FR", "13/01/2017")]
[InlineData("D", "fr-FR", "vendredi 13 janvier 2017")]
[InlineData("f", "fr-FR", "vendredi 13 janvier 2017 05:25")]
[InlineData("F", "fr-FR", "vendredi 13 janvier 2017 05:25:08")]
[InlineData("g", "fr-FR", "13/01/2017 05:25")]
[InlineData("G", "fr-FR", "13/01/2017 05:25:08")]
[InlineData("M", "fr-FR", "13 janvier")]
[InlineData("O", "fr-FR", "2017-01-13T05:25:08.2003629+00:00")]
[InlineData("R", "fr-FR", "Fri, 13 Jan 2017 05:25:08 GMT")]
[InlineData("s", "fr-FR", "2017-01-13T05:25:08")]
[InlineData("t", "fr-FR", "05:25")]
[InlineData("T", "fr-FR", "05:25:08")]
[InlineData("u", "fr-FR", "2017-01-13 05:25:08Z")]
[InlineData("U", "fr-FR", "vendredi 13 janvier 2017 05:25:08")]
[InlineData("Y", "fr-FR", "janvier 2017")]
[InlineData("d", "ko-KR", "2017-01-13")]
[InlineData("D", "ko-KR", "2017년 1월 13일 금요일")]
[InlineData("f", "ko-KR", "2017년 1월 13일 금요일 오전 5:32")]
[InlineData("F", "ko-KR", "2017년 1월 13일 금요일 오전 5:32:06")]
[InlineData("g", "ko-KR", "2017-01-13 오전 5:32")]
[InlineData("G", "ko-KR", "2017-01-13 오전 5:32:06")]
[InlineData("M", "ko-KR", "1월 13일")]
[InlineData("O", "ko-KR", "2017-01-13T05:32:06.6865069+00:00")]
[InlineData("R", "ko-KR", "Fri, 13 Jan 2017 05:32:06 GMT")]
[InlineData("s", "ko-KR", "2017-01-13T05:32:06")]
[InlineData("t", "ko-KR", "오전 5:32")]
[InlineData("T", "ko-KR", "오전 5:32:06")]
[InlineData("u", "ko-KR", "2017-01-13 05:32:06Z")]
[InlineData("U", "ko-KR", "2017년 1월 13일 금요일 오전 5:32:06")]
[InlineData("Y", "ko-KR", "2017년 1월")]
public void Given_Yaml_WithLocaleAndTimeFormat_ReadYaml_ShouldReturn_Result(string format, string locale, string value)
{
var culture = CultureInfo.GetCultureInfo(locale);
var expected = DateTime.ParseExact(value, format, culture, DateTimeStyles.AssumeUniversal).ToUniversalTime();
var converter = new DateTimeConverter(provider: culture, formats: new[] { "d", "D", "f", "F", "g", "G", "M", "O", "R", "s", "t", "T", "u", "U", "Y" });

var parser = A.Fake<IParser>();
A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(value));

var result = converter.ReadYaml(parser, typeof(DateTime));

result.Should().Be(expected);
}

/// <summary>
/// Tests whether the ReadYaml() method should return expected result or not.
/// </summary>
Expand All @@ -314,7 +399,7 @@ public void Given_Yaml_WithTimeFormat_ReadYaml_ShouldReturn_Result(string format
public void Given_Yaml_WithTimeFormatAndLocal1_ReadYaml_ShouldReturn_Result(string format, string value)
{
var expected = DateTime.ParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal).ToLocalTime();
var converter = new DateTimeConverter(DateTimeKind.Local, new[] { "d", "D", "f", "F", "g", "G", "M", "O", "R", "s", "t", "T", "u", "Y" });
var converter = new DateTimeConverter(DateTimeKind.Local, formats: new[] { "d", "D", "f", "F", "g", "G", "M", "O", "R", "s", "t", "T", "u", "Y" });

var parser = A.Fake<IParser>();
A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(value));
Expand Down Expand Up @@ -348,7 +433,7 @@ public void Given_Yaml_WithTimeFormatAndLocal1_ReadYaml_ShouldReturn_Result(stri
public void Given_Yaml_WithTimeFormatAndLocal2_ReadYaml_ShouldReturn_Result(string format, string value)
{
var expected = DateTime.ParseExact(value, format, CultureInfo.InvariantCulture, DateTimeStyles.AssumeLocal).ToLocalTime();
var converter = new DateTimeConverter(DateTimeKind.Local, new[] { "d", "D", "f", "g", "G", "M", "O", "R", "s", "t", "T", "u", "U", "Y" });
var converter = new DateTimeConverter(DateTimeKind.Local, formats: new[] { "d", "D", "f", "g", "G", "M", "O", "R", "s", "t", "T", "u", "U", "Y" });

var parser = A.Fake<IParser>();
A.CallTo(() => parser.Current).ReturnsLazily(() => new Scalar(value));
Expand All @@ -371,7 +456,7 @@ public void Given_Yaml_WithTimeFormatAndLocal2_ReadYaml_ShouldReturn_Result(stri
/// <remarks>The converter instance uses its default parameters of "G" and UTC.</remarks>
[Theory]
[InlineData(2016, 12, 31, 3, 0, 0, DateTimeKind.Utc)]
public void Given_Values_WriteYame_ShouldReturn_Result(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind)
public void Given_Values_WriteYaml_ShouldReturn_Result(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind)
{
var dt = new DateTime(year, month, day, hour, minute, second, kind);
var formatted = dt.ToString("G", CultureInfo.InvariantCulture);
Expand All @@ -388,6 +473,39 @@ public void Given_Values_WriteYame_ShouldReturn_Result(int year, int month, int
serialised.Should().ContainEquivalentOf($"datetime: {formatted}");
}

/// <summary>
/// Tests whether the WriteYaml method should return expected result or not.
/// </summary>
/// <param name="year">Year value.</param>
/// <param name="month">Month value.</param>
/// <param name="day">Day value.</param>
/// <param name="hour">Hour value.</param>
/// <param name="minute">Minute value.</param>
/// <param name="second">Second value.</param>
/// <param name="kind"><see cref="DateTimeKind"/> value</param>
/// <param name="locale">Locale value.</param>
/// <remarks>The converter instance uses its default parameters of "G" and UTC.</remarks>
[Theory]
[InlineData(2016, 12, 31, 3, 0, 0, DateTimeKind.Utc, "es-ES")]
[InlineData(2016, 12, 31, 3, 0, 0, DateTimeKind.Utc, "ko-KR")]
public void Given_Values_WithLocale_WriteYaml_ShouldReturn_Result(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind, string locale)
{
var dt = new DateTime(year, month, day, hour, minute, second, kind);
var culture = CultureInfo.GetCultureInfo(locale);
var formatted = dt.ToString("G", culture);
var obj = new TestObject() { DateTime = dt };

var builder = new SerializerBuilder();
builder.WithNamingConvention(new CamelCaseNamingConvention());
builder.WithTypeConverter(new DateTimeConverter(provider: culture));

var serialiser = builder.Build();

var serialised = serialiser.Serialize(obj);

serialised.Should().ContainEquivalentOf($"datetime: {formatted}");
}

/// <summary>
/// Tests whether the WriteYaml method should return expected result or not.
/// </summary>
Expand All @@ -401,7 +519,7 @@ public void Given_Values_WriteYame_ShouldReturn_Result(int year, int month, int
/// <remarks>The converter instance uses its default parameters of "G" and UTC.</remarks>
[Theory]
[InlineData(2016, 12, 31, 3, 0, 0, DateTimeKind.Utc)]
public void Given_Values_WithFormats_WriteYame_ShouldReturn_Result_WithFirstFormat(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind)
public void Given_Values_WithFormats_WriteYaml_ShouldReturn_Result_WithFirstFormat(int year, int month, int day, int hour, int minute, int second, DateTimeKind kind)
{
var dt = new DateTime(year, month, day, hour, minute, second, kind);
var format = "yyyy-MM-dd HH:mm:ss";
Expand All @@ -410,7 +528,7 @@ public void Given_Values_WithFormats_WriteYame_ShouldReturn_Result_WithFirstForm

var builder = new SerializerBuilder();
builder.WithNamingConvention(new CamelCaseNamingConvention());
builder.WithTypeConverter(new DateTimeConverter(kind, new [] {format, "G"}));
builder.WithTypeConverter(new DateTimeConverter(kind, formats: new [] {format, "G"}));

var serialiser = builder.Build();

Expand Down
23 changes: 13 additions & 10 deletions YamlDotNet/Serialization/Converters/DateTimeConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,22 @@ namespace YamlDotNet.Serialization.Converters
/// </summary>
public class DateTimeConverter : IYamlTypeConverter
{
private readonly DateTimeKind _kind;
private readonly string[] _formats;
private readonly DateTimeKind kind;
private readonly IFormatProvider provider;
private readonly string[] formats;

/// <summary>
/// Initializes a new instance of the <see cref="DateTimeConverter"/> class.
/// </summary>
/// <param name="kind"><see cref="DateTimeKind"/> value. Default value is <see cref="DateTimeKind.Utc"/>. <see cref="DateTimeKind.Unspecified"/> is considered as <see cref="DateTimeKind.Utc"/>.</param>
/// <param name="provider"><see cref="IFormatProvider"/> instance. Default value is <see cref="CultureInfo.InvariantCulture"/>.</param>
/// <param name="formats">List of date/time formats for parsing. Default value is "<c>G</c>".</param>
/// <remarks>On deserializing, all formats in the list are used for conversion, while on serializing, the first format in the list is used.</remarks>
public DateTimeConverter(DateTimeKind kind = DateTimeKind.Utc, params string[] formats)
public DateTimeConverter(DateTimeKind kind = DateTimeKind.Utc, IFormatProvider provider = null, params string[] formats)
{
this._kind = kind == DateTimeKind.Unspecified ? DateTimeKind.Utc : kind;
this._formats = formats.DefaultIfEmpty("G").ToArray();
this.kind = kind == DateTimeKind.Unspecified ? DateTimeKind.Utc : kind;
this.provider = provider ?? CultureInfo.InvariantCulture;
this.formats = formats.DefaultIfEmpty("G").ToArray();
}

/// <summary>
Expand All @@ -68,10 +71,10 @@ public bool Accepts(Type type)
public object ReadYaml(IParser parser, Type type)
{
var value = ((Scalar)parser.Current).Value;
var style = this._kind == DateTimeKind.Local ? DateTimeStyles.AssumeLocal : DateTimeStyles.AssumeUniversal;
var style = this.kind == DateTimeKind.Local ? DateTimeStyles.AssumeLocal : DateTimeStyles.AssumeUniversal;

var dt = DateTime.ParseExact(value, this._formats, CultureInfo.InvariantCulture, style);
dt = EnsureDateTimeKind(dt, this._kind);
var dt = DateTime.ParseExact(value, this.formats, this.provider, style);
dt = EnsureDateTimeKind(dt, this.kind);

parser.MoveNext();

Expand All @@ -88,8 +91,8 @@ public object ReadYaml(IParser parser, Type type)
public void WriteYaml(IEmitter emitter, object value, Type type)
{
var dt = (DateTime) value;
var adjusted = this._kind == DateTimeKind.Local ? dt.ToLocalTime() : dt.ToUniversalTime();
var formatted = adjusted.ToString(this._formats.First(), CultureInfo.InvariantCulture); // Always take the first format of the list.
var adjusted = this.kind == DateTimeKind.Local ? dt.ToLocalTime() : dt.ToUniversalTime();
var formatted = adjusted.ToString(this.formats.First(), this.provider); // Always take the first format of the list.

emitter.Emit((ParsingEvent)new Scalar((string)null, (string)null, formatted, ScalarStyle.Any, true, false));
}
Expand Down

0 comments on commit 3158fa3

Please sign in to comment.