Skip to content
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

Fix issue when fromat string starts with [$-F800] and [$-F400] #42

Open
wants to merge 1 commit into
base: master
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
20 changes: 15 additions & 5 deletions src/ExcelNumberFormat/Formatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ static public string Format(object value, Section node, CultureInfo culture, boo
case SectionType.Date:
if (ExcelDateTime.TryConvert(value, isDate1904, culture, out var excelDateTime))
{
return FormatDate(excelDateTime, node.GeneralTextDateDurationParts, culture);
return FormatDate(excelDateTime, node.GeneralTextDateDurationParts, culture, node.Lcid);
}
else
{
Expand All @@ -45,12 +45,12 @@ static public string Format(object value, Section node, CultureInfo culture, boo
case SectionType.Duration:
if (value is TimeSpan ts)
{
return FormatTimeSpan(ts, node.GeneralTextDateDurationParts, culture);
return FormatTimeSpan(ts, node.GeneralTextDateDurationParts, culture, node.Lcid);
}
else
{
var d = Convert.ToDouble(value);
return FormatTimeSpan(TimeSpan.FromDays(d), node.GeneralTextDateDurationParts, culture);
return FormatTimeSpan(TimeSpan.FromDays(d), node.GeneralTextDateDurationParts, culture, node.Lcid);
}

case SectionType.General:
Expand Down Expand Up @@ -86,8 +86,13 @@ static string FormatGeneralText(string text, List<string> tokens)
return result.ToString();
}

private static string FormatTimeSpan(TimeSpan timeSpan, List<string> tokens, CultureInfo culture)
private static string FormatTimeSpan(TimeSpan timeSpan, List<string> tokens, CultureInfo culture, WindowsLanguageCodeIdentifier lcid)
{
if (lcid != null && lcid.IsLongSystemTime && lcid.TimeTokens?.Count > 0)
{
tokens = lcid.TimeTokens;
}

// NOTE/TODO: assumes there is exactly one [hh], [mm] or [ss] using the integer part of TimeSpan.TotalXXX when formatting.
// The timeSpan input is then truncated to the remainder fraction, which is used to format mm and/or ss.
var result = new StringBuilder();
Expand Down Expand Up @@ -154,8 +159,13 @@ private static string FormatTimeSpan(TimeSpan timeSpan, List<string> tokens, Cul
return result.ToString();
}

private static string FormatDate(ExcelDateTime date, List<string> tokens, CultureInfo culture)
private static string FormatDate(ExcelDateTime date, List<string> tokens, CultureInfo culture, WindowsLanguageCodeIdentifier lcid)
{
if (lcid != null && lcid.IsLongSystemDate)
{
return date.ToString(culture.DateTimeFormat.LongDatePattern, culture);
}

var containsAmPm = ContainsAmPm(tokens);

var result = new StringBuilder();
Expand Down
49 changes: 48 additions & 1 deletion src/ExcelNumberFormat/Parser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private static Section ParseSection(Tokenizer reader, int index, out bool syntax
bool hasPlaceholders = false;
Condition condition = null;
Color color = null;
WindowsLanguageCodeIdentifier localeIdentifier = null;
string token;
List<string> tokens = new List<string>();

Expand Down Expand Up @@ -72,6 +73,8 @@ private static Section ParseSection(Tokenizer reader, int index, out bool syntax
condition = parseCondition;
else if (TryParseColor(expression, out var parseColor))
color = parseColor;
else if (TryParseLocaleIdentifier(expression, out var parseLocaleIdentifier))
localeIdentifier = parseLocaleIdentifier;
else if (TryParseCurrencySymbol(expression, out var parseCurrencySymbol))
tokens.Add("\"" + parseCurrencySymbol + "\"");
}
Expand Down Expand Up @@ -153,7 +156,8 @@ private static Section ParseSection(Tokenizer reader, int index, out bool syntax
Fraction = fraction,
Exponential = exponential,
Number = number,
GeneralTextDateDurationParts = generalTextDateDuration
GeneralTextDateDurationParts = generalTextDateDuration,
Lcid = localeIdentifier
};
}

Expand Down Expand Up @@ -394,5 +398,48 @@ private static bool TryParseCurrencySymbol(string token, out string currencySymb

return true;
}

private static bool TryParseLocaleIdentifier(string token, out WindowsLanguageCodeIdentifier lcid)
{
if (string.IsNullOrEmpty(token)
|| !token.StartsWith("$-"))
{
lcid = null;
return false;
}

var localeStr = token.Substring(2);
if (!int.TryParse(localeStr, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out int localeId))
{
lcid = null;
return false;
}

lcid = new WindowsLanguageCodeIdentifier { LocaleId = localeId };

if (!lcid.IsLongSystemTime) return true;

// because in .net 2.0, TimeSpan does not have a ToString method with arguments, we will parse here the long time pattern to get the tokens that will be used in the parser.

var timeTokensList = new List<string>();
var tokenizer = new Tokenizer(CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern);
string timeToken;
bool syntaxError;

while ((timeToken = ReadToken(tokenizer, out syntaxError)) != null)
{
if (syntaxError)
{
break;
}
timeTokensList.Add(timeToken);
}

if (!syntaxError)
{
lcid.TimeTokens = timeTokensList;
}
return true;
}
}
}
2 changes: 2 additions & 0 deletions src/ExcelNumberFormat/Section.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,7 @@ internal class Section
public DecimalSection Number { get; set; }

public List<string> GeneralTextDateDurationParts { get; set; }

public WindowsLanguageCodeIdentifier Lcid { get; set; }
}
}
21 changes: 21 additions & 0 deletions src/ExcelNumberFormat/WindowsLanguageCodeIdentifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Collections.Generic;

namespace ExcelNumberFormat
{
/// <summary>
/// Represents a Windows Language Code Identifier (LCID) as defined by [MS-LCID] (https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f)
/// </summary>
internal class WindowsLanguageCodeIdentifier
{
public int LocaleId { get; set; }

public bool IsLongSystemDate => LocaleId == 0xF800;

public bool IsLongSystemTime => LocaleId == 0xF400;

/// <summary>
/// If IsLongSystemTime is true, then contains the tokens for the time formatter.
/// </summary>
public List<string> TimeTokens { get; set; }
}
}
13 changes: 12 additions & 1 deletion test/ExcelNumberFormat.Tests/Class1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public void TestIsDateFormatString()
Assert.IsTrue(IsDateFormatString("mm:ss"));
Assert.IsTrue(IsDateFormatString("mm:ss.0"));
Assert.IsTrue(IsDateFormatString("[$-809]dd mmmm yyyy"));
Assert.IsTrue(IsDateFormatString(@"[$-F800]dddd\,\ mmmm\ dd\,\ yyyy"));
Assert.IsFalse(IsDateFormatString("#,##0;[Red]-#,##0"));
Assert.IsFalse(IsDateFormatString("0_);[Red](0)"));
Assert.IsFalse(IsDateFormatString(@"0\h"));
Expand Down Expand Up @@ -143,6 +144,9 @@ public void TestDate()
Test(new DateTime(2017, 10, 16, 0, 0, 0), "dddd,,, MMMM d,, yyyy,,,,", "Monday, October 16, 2017,");
Test(new DateTime(2020, 1, 1, 0, 35, 55), "m/d/yyyy\\ hh:mm:ss AM/PM;@", "1/1/2020 12:35:55 AM");
Test(new DateTime(2020, 1, 1, 12, 35, 55), "m/d/yyyy\\ hh:mm:ss AM/PM;@", "1/1/2020 12:35:55 PM");
Test(new DateTime(2017, 10, 16, 0, 0, 0), "[$-F800]m/d/yyyy\\ h:mm:ss a/P;@", "Monday, 16 October 2017");
Test(new DateTime(2017, 10, 16, 0, 0, 0), @"[$-F800]dddd\,\ mmmm\ dd\,\ yyyy", "Monday, 16 October 2017");
TestWithCulture(new CultureInfo("Fr-fr"), new DateTime(2017, 10, 16, 0, 0, 0), @"[$-F800]dddd\,\ mmmm\ dd\,\ yyyy", "lundi 16 octobre 2017");
}

[TestMethod]
Expand Down Expand Up @@ -192,11 +196,18 @@ public void TestTimeSpan()
Test(new TimeSpan(0, -2, -31, -45), "[hh]:mm:ss", "-02:31:45");
Test(new TimeSpan(0, -2, -31, -44, -500), "[hh]:mm:ss", "-02:31:45");
Test(new TimeSpan(0, -2, -31, -44, -500), "[hh]:mm:ss.000", "-02:31:44.500");
Test(new TimeSpan(1, 2, 31, 44, 500), @"[$-F400]h:mm:ss\ AM/PM", "1.02:31:44.5000000");
TestWithCulture(new CultureInfo("Fr-fr"),new TimeSpan(1, 2, 31, 44, 500), @"[$-F400]h:mm:ss\ AM/PM", "1.02:31:44.5000000");
}

void Test(object value, string format, string expected, bool isDate1904 = false)
{
var result = Format(value, format, CultureInfo.InvariantCulture, isDate1904);
TestWithCulture(CultureInfo.InvariantCulture, value, format, expected, isDate1904);
}

void TestWithCulture(CultureInfo culture, object value, string format, string expected, bool isDate1904 = false)
{
var result = Format(value, format, culture, isDate1904);
Assert.AreEqual(expected, result);
}

Expand Down