diff --git a/src/SmartFormat.Extensions.Time/Resources/de.json b/src/SmartFormat.Extensions.Time/Resources/de.json
new file mode 100644
index 00000000..857a7311
--- /dev/null
+++ b/src/SmartFormat.Extensions.Time/Resources/de.json
@@ -0,0 +1,16 @@
+{
+ "PluralRule": "de",
+ "Ptxt_week": [ "{0} Woche", "{0} Wochen" ],
+ "Ptxt_day": [ "{0} Tag", "{0} Tage" ],
+ "Ptxt_hour": [ "{0} Stunde", "{0} Stunden" ],
+ "Ptxt_minute": [ "{0} Minute", "{0} Minuten" ],
+ "Ptxt_second": [ "{0} Sekunde", "{0} Sekunden" ],
+ "Ptxt_millisecond": [ "{0} Millisekunde", "{0} Millisekunden" ],
+ "Ptxt_w": [ "{0}w" ],
+ "Ptxt_d": [ "{0}t" ],
+ "Ptxt_h": [ "{0}h" ],
+ "Ptxt_m": [ "{0}m" ],
+ "Ptxt_s": [ "{0}s" ],
+ "Ptxt_ms": [ "{0}ms" ],
+ "Ptxt_lessThan": "weniger als {0}"
+}
\ No newline at end of file
diff --git a/src/SmartFormat.Extensions.Time/Resources/en.json b/src/SmartFormat.Extensions.Time/Resources/en.json
new file mode 100644
index 00000000..ae0c49a7
--- /dev/null
+++ b/src/SmartFormat.Extensions.Time/Resources/en.json
@@ -0,0 +1,16 @@
+{
+ "PluralRule": "en",
+ "Ptxt_week": [ "{0} week", "{0} weeks" ],
+ "Ptxt_day": [ "{0} day", "{0} days" ],
+ "Ptxt_hour": [ "{0} hour", "{0} hours" ],
+ "Ptxt_minute": [ "{0} minute", "{0} minutes" ],
+ "Ptxt_second": [ "{0} second", "{0} seconds" ],
+ "Ptxt_millisecond": [ "{0} millisecond", "{0} milliseconds" ],
+ "Ptxt_w": [ "{0}w" ],
+ "Ptxt_d": [ "{0}d" ],
+ "Ptxt_h": [ "{0}h" ],
+ "Ptxt_m": [ "{0}m" ],
+ "Ptxt_s": [ "{0}s" ],
+ "Ptxt_ms": [ "{0}ms" ],
+ "Ptxt_lessThan": "less than {0}"
+}
\ No newline at end of file
diff --git a/src/SmartFormat.Extensions.Time/Resources/es.json b/src/SmartFormat.Extensions.Time/Resources/es.json
new file mode 100644
index 00000000..5dc9735a
--- /dev/null
+++ b/src/SmartFormat.Extensions.Time/Resources/es.json
@@ -0,0 +1,16 @@
+{
+ "PluralRule": "es",
+ "Ptxt_week": [ "{0} semana", "{0} semanas" ],
+ "Ptxt_day": [ "{0} día", "{0} días" ],
+ "Ptxt_hour": [ "{0} hora", "{0} horas" ],
+ "Ptxt_minute": [ "{0} minuto", "{0} minutos" ],
+ "Ptxt_second": [ "{0} segundo", "{0} segundos" ],
+ "Ptxt_millisecond": [ "{0} milisegundo", "{0} milisegundos" ],
+ "Ptxt_w": [ "{0}sem" ],
+ "Ptxt_d": [ "{0}d" ],
+ "Ptxt_h": [ "{0}h" ],
+ "Ptxt_m": [ "{0}m" ],
+ "Ptxt_s": [ "{0}s" ],
+ "Ptxt_ms": [ "{0}ms" ],
+ "Ptxt_lessThan": "menos que {0}"
+}
diff --git a/src/SmartFormat.Extensions.Time/Resources/fr.json b/src/SmartFormat.Extensions.Time/Resources/fr.json
new file mode 100644
index 00000000..59ec1084
--- /dev/null
+++ b/src/SmartFormat.Extensions.Time/Resources/fr.json
@@ -0,0 +1,16 @@
+{
+ "PluralRule": "fr",
+ "Ptxt_week": [ "{0} semaine", "{0} semaines" ],
+ "Ptxt_day": [ "{0} jour", "{0} jours" ],
+ "Ptxt_hour": [ "{0} heure", "{0} heures" ],
+ "Ptxt_minute": [ "{0} minute", "{0} minutes" ],
+ "Ptxt_second": [ "{0} seconde", "{0} secondes" ],
+ "Ptxt_millisecond": [ "{0} milliseconde", "{0} millisecondes" ],
+ "Ptxt_w": [ "{0}sem" ],
+ "Ptxt_d": [ "{0}j" ],
+ "Ptxt_h": [ "{0}h" ],
+ "Ptxt_m": [ "{0}m" ],
+ "Ptxt_s": [ "{0}s" ],
+ "Ptxt_ms": [ "{0}ms" ],
+ "Ptxt_lessThan": "moins que {0}"
+}
\ No newline at end of file
diff --git a/src/SmartFormat.Extensions.Time/Resources/it.json b/src/SmartFormat.Extensions.Time/Resources/it.json
new file mode 100644
index 00000000..1a2e8d5f
--- /dev/null
+++ b/src/SmartFormat.Extensions.Time/Resources/it.json
@@ -0,0 +1,16 @@
+{
+ "PluralRule": "it",
+ "Ptxt_week": [ "{0} settimana", "{0} settimane" ],
+ "Ptxt_day": [ "{0} giorno", "{0} giorni" ],
+ "Ptxt_hour": [ "{0} ora", "{0} ore" ],
+ "Ptxt_minute": [ "{0} minuto", "{0} minuti" ],
+ "Ptxt_second": [ "{0} secondo", "{0} secondi" ],
+ "Ptxt_millisecond": [ "{0} millisecondo", "{0} millisecondi" ],
+ "Ptxt_w": [ "{0}set" ],
+ "Ptxt_d": [ "{0}g" ],
+ "Ptxt_h": [ "{0}h" ],
+ "Ptxt_m": [ "{0}m" ],
+ "Ptxt_s": [ "{0}s" ],
+ "Ptxt_ms": [ "{0}ms" ],
+ "Ptxt_lessThan": "meno di {0}"
+}
diff --git a/src/SmartFormat.Extensions.Time/Resources/pt.json b/src/SmartFormat.Extensions.Time/Resources/pt.json
new file mode 100644
index 00000000..5310bcba
--- /dev/null
+++ b/src/SmartFormat.Extensions.Time/Resources/pt.json
@@ -0,0 +1,16 @@
+{
+ "PluralRule": "pt",
+ "Ptxt_week": [ "{0} semana", "{0} semanas" ],
+ "Ptxt_day": [ "{0} dia", "{0} dias" ],
+ "Ptxt_hour": [ "{0} hora", "{0} horas" ],
+ "Ptxt_minute": [ "{0} minuto", "{0} minutos" ],
+ "Ptxt_second": [ "{0} segundo", "{0} segundos" ],
+ "Ptxt_millisecond": [ "{0} milissegundo", "{0} milissegundos" ],
+ "Ptxt_w": [ "{0}sem" ],
+ "Ptxt_d": [ "{0}d" ],
+ "Ptxt_h": [ "{0}h" ],
+ "Ptxt_m": [ "{0}m" ],
+ "Ptxt_s": [ "{0}s" ],
+ "Ptxt_ms": [ "{0}ms" ],
+ "Ptxt_lessThan": "menos do que {0}"
+}
diff --git a/src/SmartFormat.Extensions.Time/SmartFormat.Extensions.Time.csproj b/src/SmartFormat.Extensions.Time/SmartFormat.Extensions.Time.csproj
index a9ff6e95..51edb255 100644
--- a/src/SmartFormat.Extensions.Time/SmartFormat.Extensions.Time.csproj
+++ b/src/SmartFormat.Extensions.Time/SmartFormat.Extensions.Time.csproj
@@ -13,6 +13,22 @@ It uses extensions to provide named placeholders, localization, pluralization, g
string-format stringformat template templating string-composition smartformat smart-format netstandard netcore netframework csharp c-sharp
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -24,6 +40,7 @@ It uses extensions to provide named placeholders, localization, pluralization, g
all
runtime; build; native; contentfiles; analyzers; buildtransitive
+
diff --git a/src/SmartFormat.Extensions.Time/TimeFormatter.cs b/src/SmartFormat.Extensions.Time/TimeFormatter.cs
index 1fe7f16b..23cad2cf 100644
--- a/src/SmartFormat.Extensions.Time/TimeFormatter.cs
+++ b/src/SmartFormat.Extensions.Time/TimeFormatter.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using SmartFormat.Core.Extensions;
@@ -24,13 +25,14 @@ public class TimeFormatter : IFormatter
/// Obsolete. s only have one unique name.
///
[Obsolete("Use property \"Name\" instead", true)]
+ [ExcludeFromCodeCoverage]
public string[] Names { get; set; } = {"timespan", "time", string.Empty};
///
public string Name { get; set; } = "time";
///
- public bool CanAutoDetect { get; set; } = true;
+ public bool CanAutoDetect { get; set; } = false;
#region Constructors
@@ -60,6 +62,7 @@ public TimeFormatter()
/// makes use of and .
///
[Obsolete("This constructor is not required. Changed process to determine the default culture.", true)]
+ [ExcludeFromCodeCoverage]
public TimeFormatter(string defaultTwoLetterLanguageName)
{
if (CommonLanguagesTimeTextInfo.GetTimeTextInfo(defaultTwoLetterLanguageName) == null)
@@ -111,6 +114,7 @@ public string FallbackLanguage
/// 3. The .
///
[Obsolete("This property is not supported any more. Changed process to get or set the default culture.", true)]
+ [ExcludeFromCodeCoverage]
public string DefaultTwoLetterISOLanguageName { get; set; } = "en";
#endregion
@@ -121,19 +125,37 @@ public string FallbackLanguage
public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
{
var format = formattingInfo.Format;
- var current = formattingInfo.CurrentValue;
+
+ // Auto-detection calls just return a failure to evaluate
+ if (string.IsNullOrEmpty(formattingInfo.Placeholder?.FormatterName))
+ return false;
+
+#if NET6_0_OR_GREATER
+ if (formattingInfo.CurrentValue is not (TimeSpan or DateTime or DateTimeOffset or TimeOnly))
+ throw new FormattingException(formattingInfo.Format?.Items.FirstOrDefault(),
+ $"'{nameof(TimeFormatter)}' can only process types of " +
+ $"{nameof(TimeSpan)}, {nameof(DateTime)}, {nameof(DateTimeOffset)}, {nameof(TimeOnly)}, " +
+ $"but not '{formattingInfo.CurrentValue?.GetType()}'", 0);
+#else
+ if (formattingInfo.CurrentValue is not (TimeSpan or DateTime or DateTimeOffset))
+ throw new FormattingException(formattingInfo.Format?.Items.FirstOrDefault(),
+ $"'{nameof(TimeFormatter)}' can only process types of " +
+ $"{nameof(TimeSpan)}, {nameof(DateTime)}, {nameof(DateTimeOffset)}, " +
+ $"but not '{formattingInfo.CurrentValue?.GetType()}'", 0);
+#endif
// Now we have to check for a nested format.
// That is the one needed for the ListFormatter
var timeParts = GetTimeParts(formattingInfo);
if (timeParts is null) return false;
- if (format is { Length: > 0, HasNested: true })
+ if (format is { Length: > 1, HasNested: true })
{
- current = timeParts; // must be an IList to work with ListFormatter
-
- format.Items.RemoveAt(0); // That's the format for the TimeFormatter
- formattingInfo.FormatAsChild(format, current);
+ // Remove the format for the TimeFormatter
+ format.Items.RemoveAt(0);
+ // Try to invoke the child format - usually the ListFormatter
+ // to format the list of time parts
+ formattingInfo.FormatAsChild(format, timeParts);
return true;
}
@@ -145,39 +167,28 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
{
var format = formattingInfo.Format;
var formatterName = formattingInfo.Placeholder?.FormatterName ?? string.Empty;
+
var current = formattingInfo.CurrentValue;
var options = formattingInfo.FormatterOptions.Trim();
var formatText = format?.RawText.Trim() ?? string.Empty;
- // Not clear, whether we can process this format
- if (formatterName == string.Empty && options == string.Empty && formatText == string.Empty) return null;
-
// In SmartFormat 2.x, the format could be included in options, with empty format.
// Using compatibility with v2, there is no reliable way to set a language as an option
var v2Compatibility = options != string.Empty && formatText == string.Empty;
var formattingOptions = v2Compatibility ? options : formatText;
- var fromTime = GetFromTime(current, formattingOptions);
+ var fromTime = GetFromTime(current);
- if (fromTime is null)
- {
- // Auto detection calls just return a failure to evaluate
- if (formatterName == string.Empty)
- return null;
-
- // throw, if the formatter has been called explicitly
- throw new FormatException(
- $"Formatter named '{formatterName}' can only process types of {nameof(TimeSpan)}, {nameof(DateTime)}, {nameof(DateTimeOffset)}");
- }
+ if (fromTime == null) return null;
var timeTextInfo = GetTimeTextInfo(formattingInfo, v2Compatibility);
- var timeSpanFormatOptions = TimeSpanFormatOptionsConverter.Parse(v2Compatibility ? options : formatText);
+ var timeSpanFormatOptions = TimeSpanFormatOptionsConverter.Parse(formattingOptions);
return fromTime.Value.ToTimeParts(timeSpanFormatOptions, timeTextInfo);
}
- private static TimeSpan? GetFromTime(object? current, string? formattingOptions)
+ private static TimeSpan? GetFromTime(object? current)
{
TimeSpan? fromTime = null;
@@ -186,17 +197,16 @@ public bool TryEvaluateFormat(IFormattingInfo formattingInfo)
case TimeSpan timeSpan:
fromTime = timeSpan;
break;
+#if NET6_0_OR_GREATER
+ case TimeOnly timeOnly:
+ fromTime = timeOnly.ToTimeSpan();
+ break;
+#endif
case DateTime dateTime:
- if (formattingOptions != string.Empty)
- {
- fromTime = SystemTime.Now().ToUniversalTime().Subtract(dateTime.ToUniversalTime());
- }
+ fromTime = SystemTime.Now().ToUniversalTime().Subtract(dateTime.ToUniversalTime());
break;
case DateTimeOffset dateTimeOffset:
- if (formattingOptions != string.Empty)
- {
- fromTime = SystemTime.OffsetNow().UtcDateTime.Subtract(dateTimeOffset.UtcDateTime);
- }
+ fromTime = SystemTime.OffsetNow().UtcDateTime.Subtract(dateTimeOffset.UtcDateTime);
break;
}
@@ -224,7 +234,7 @@ private TimeTextInfo GetTimeTextInfo(IFormattingInfo formattingInfo, bool v2Comp
throw new ArgumentException($"{nameof(TimeTextInfo)} could not be found for the given {nameof(IFormatProvider)}.", nameof(formattingInfo));
}
- #endregion
+#endregion
private static CultureInfo GetCultureInfo(IFormattingInfo formattingInfo, bool v2Compatibility)
{
diff --git a/src/SmartFormat.Extensions.Time/Utilities/CommonLanguagesTimeTextInfo.cs b/src/SmartFormat.Extensions.Time/Utilities/CommonLanguagesTimeTextInfo.cs
index c3a1b709..22c54e58 100644
--- a/src/SmartFormat.Extensions.Time/Utilities/CommonLanguagesTimeTextInfo.cs
+++ b/src/SmartFormat.Extensions.Time/Utilities/CommonLanguagesTimeTextInfo.cs
@@ -2,7 +2,12 @@
// Copyright SmartFormat Project maintainers and contributors.
// Licensed under the MIT license.
+using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Reflection;
+using System.Text.Json;
using SmartFormat.Utilities;
namespace SmartFormat.Extensions.Time.Utilities;
@@ -17,129 +22,45 @@ public static class CommonLanguagesTimeTextInfo
///
/// Gets the for the English language.
///
- public static TimeTextInfo English => new()
- {
- PluralRule = PluralRules.GetPluralRule("en"),
- Ptxt_week = new[] { "{0} week", "{0} weeks" },
- Ptxt_day = new[] { "{0} day", "{0} days" },
- Ptxt_hour = new[] { "{0} hour", "{0} hours" },
- Ptxt_minute = new[] { "{0} minute", "{0} minutes" },
- Ptxt_second = new[] { "{0} second", "{0} seconds" },
- Ptxt_millisecond = new[] { "{0} millisecond", "{0} milliseconds" },
- Ptxt_w = new[] { "{0}w" },
- Ptxt_d = new[] { "{0}d" },
- Ptxt_h = new[] { "{0}h" },
- Ptxt_m = new[] { "{0}m" },
- Ptxt_s = new[] { "{0}s" },
- Ptxt_ms = new[] { "{0}ms" },
- Ptxt_lessThan = "less than {0}"
- };
-
+ [Obsolete("Use GetTimeTextInfo(\"en\") instead")]
+ [ExcludeFromCodeCoverage]
+ public static TimeTextInfo English => GetTimeTextInfo("en")!;
+
///
/// Gets the for the French language.
///
- public static TimeTextInfo French => new()
- {
- PluralRule = PluralRules.GetPluralRule("fr"),
- Ptxt_week = new[] { "{0} semaine", "{0} semaines" },
- Ptxt_day = new[] { "{0} jour", "{0} jours" },
- Ptxt_hour = new[] { "{0} heure", "{0} heures" },
- Ptxt_minute = new[] { "{0} minute", "{0} minutes" },
- Ptxt_second = new[] { "{0} seconde", "{0} secondes" },
- Ptxt_millisecond = new[] { "{0} milliseconde", "{0} millisecondes" },
- Ptxt_w = new[] { "{0}sem" },
- Ptxt_d = new[] { "{0}j" },
- Ptxt_h = new[] { "{0}h" },
- Ptxt_m = new[] { "{0}m" },
- Ptxt_s = new[] { "{0}s" },
- Ptxt_ms = new[] { "{0}ms" },
- Ptxt_lessThan = "moins que {0}"
- };
+ [Obsolete("Use GetTimeTextInfo(\"fr\") instead")]
+ [ExcludeFromCodeCoverage]
+ public static TimeTextInfo French => GetTimeTextInfo("fr")!;
///
/// Gets the for the Spanish language.
///
- public static TimeTextInfo Spanish => new()
- {
- PluralRule = PluralRules.GetPluralRule("es"),
- Ptxt_week = new[] { "{0} semana", "{0} semanas" },
- Ptxt_day = new[] { "{0} día", "{0} días" },
- Ptxt_hour = new[] { "{0} hore", "{0} horas" },
- Ptxt_minute = new[] { "{0} minuto", "{0} minutos" },
- Ptxt_second = new[] { "{0} segundo", "{0} segundos" },
- Ptxt_millisecond = new[] { "{0} milisegundo", "{0} milisegundos" },
- Ptxt_w = new[] { "{0}sem" },
- Ptxt_d = new[] { "{0}d" },
- Ptxt_h = new[] { "{0}h" },
- Ptxt_m = new[] { "{0}m" },
- Ptxt_s = new[] { "{0}s" },
- Ptxt_ms = new[] { "{0}ms" },
- Ptxt_lessThan = "menos que {0}"
- };
+ [Obsolete("Use GetTimeTextInfo(\"es\") instead")]
+ [ExcludeFromCodeCoverage]
+ public static TimeTextInfo Spanish => GetTimeTextInfo("es")!;
///
/// Gets the for the Portuguese language.
///
- public static TimeTextInfo Portuguese => new()
- {
- PluralRule = PluralRules.GetPluralRule("pt"),
- Ptxt_week = new[] { "{0} semana", "{0} semanas" },
- Ptxt_day = new[] { "{0} dia", "{0} dias" },
- Ptxt_hour = new[] { "{0} hora", "{0} horas" },
- Ptxt_minute = new[] { "{0} minuto", "{0} minutos" },
- Ptxt_second = new[] { "{0} segundo", "{0} segundos" },
- Ptxt_millisecond = new[] { "{0} milissegundo", "{0} milissegundos" },
- Ptxt_w = new[] { "{0}sem" },
- Ptxt_d = new[] { "{0}d" },
- Ptxt_h = new[] { "{0}h" },
- Ptxt_m = new[] { "{0}m" },
- Ptxt_s = new[] { "{0}s" },
- Ptxt_ms = new[] { "{0}ms" },
- Ptxt_lessThan = "menos do que {0}"
- };
+ [Obsolete("Use GetTimeTextInfo(\"pt\") instead")]
+ [ExcludeFromCodeCoverage]
+ public static TimeTextInfo Portuguese => GetTimeTextInfo("pt")!;
///
/// Gets the for the Italian language.
///
- public static TimeTextInfo Italian => new()
- {
- PluralRule = PluralRules.GetPluralRule("it"),
- Ptxt_week = new[] { "{0} settimana", "{0} settimane" },
- Ptxt_day = new[] { "{0} giorno", "{0} giorni" },
- Ptxt_hour = new[] { "{0} ora", "{0} ore" },
- Ptxt_minute = new[] { "{0} minuto", "{0} minuti" },
- Ptxt_second = new[] { "{0} secondo", "{0} secondi" },
- Ptxt_millisecond = new[] { "{0} millisecondo", "{0} millisecondi" },
- Ptxt_w = new[] { "{0}set" },
- Ptxt_d = new[] { "{0}g" },
- Ptxt_h = new[] { "{0}h" },
- Ptxt_m = new[] { "{0}m" },
- Ptxt_s = new[] { "{0}s" },
- Ptxt_ms = new[] { "{0}ms" },
- Ptxt_lessThan = "meno di {0}"
- };
+ [Obsolete("Use GetTimeTextInfo(\"it\") instead")]
+ [ExcludeFromCodeCoverage]
+ public static TimeTextInfo Italian => GetTimeTextInfo("it")!;
///
/// Gets the for the German language.
///
- public static TimeTextInfo German => new()
- {
- PluralRule = PluralRules.GetPluralRule("de"),
- Ptxt_week = new[] { "{0} Woche", "{0} Wochen" },
- Ptxt_day = new[] { "{0} Tag", "{0} Tage" },
- Ptxt_hour = new[] { "{0} Stunde", "{0} Stunden" },
- Ptxt_minute = new[] { "{0} Minute", "{0} Minuten" },
- Ptxt_second = new[] { "{0} Sekunde", "{0} Sekunden" },
- Ptxt_millisecond = new[] { "{0} Millisekunde", "{0} Millisekunden" },
- Ptxt_w = new[] { "{0}w" },
- Ptxt_d = new[] { "{0}t" },
- Ptxt_h = new[] { "{0}h" },
- Ptxt_m = new[] { "{0}m" },
- Ptxt_s = new[] { "{0}s" },
- Ptxt_ms = new[] { "{0}ms" },
- Ptxt_lessThan = "weniger als {0}"
- };
-
+ [Obsolete("Use GetTimeTextInfo(\"de\") instead")]
+ [ExcludeFromCodeCoverage]
+ public static TimeTextInfo German => GetTimeTextInfo("de")!;
+
///
/// Adds a for a language.
///
@@ -169,15 +90,41 @@ public static void AddLanguage(string twoLetterIsoLanguageName, TimeTextInfo tim
if (_customLanguage.TryGetValue(twoLetterIsoLanguageName, out var timeTextInfo))
return timeTextInfo;
- return twoLetterIsoLanguageName switch
+ timeTextInfo = LoadTimeTextInfo(twoLetterIsoLanguageName);
+ if (timeTextInfo is null) return null;
+
+ _customLanguage.Add(twoLetterIsoLanguageName, timeTextInfo);
+ return timeTextInfo;
+ }
+
+ private static TimeTextInfo? LoadTimeTextInfo(string languageCode)
+ {
+ var assembly = Assembly.GetExecutingAssembly();
+ var resourceName = $"SmartFormat.Extensions.Time.Resources.{languageCode}.json";
+
+ using var stream = assembly.GetManifestResourceStream(resourceName);
+ if (stream == null) return null;
+
+ using var reader = new StreamReader(stream);
+ var json = reader.ReadToEnd();
+ var data = JsonSerializer.Deserialize(json);
+
+ return new TimeTextInfo
{
- "en" => English,
- "fr" => French,
- "es" => Spanish,
- "pt" => Portuguese,
- "it" => Italian,
- "de" => German,
- _ => null
+ PluralRule = PluralRules.GetPluralRule(data!.PluralRule),
+ Ptxt_week = data.Ptxt_week,
+ Ptxt_day = data.Ptxt_day,
+ Ptxt_hour = data.Ptxt_hour,
+ Ptxt_minute = data.Ptxt_minute,
+ Ptxt_second = data.Ptxt_second,
+ Ptxt_millisecond = data.Ptxt_millisecond,
+ Ptxt_w = data.Ptxt_w,
+ Ptxt_d = data.Ptxt_d,
+ Ptxt_h = data.Ptxt_h,
+ Ptxt_m = data.Ptxt_m,
+ Ptxt_s = data.Ptxt_s,
+ Ptxt_ms = data.Ptxt_ms,
+ Ptxt_lessThan = data.Ptxt_lessThan
};
}
-}
\ No newline at end of file
+}
diff --git a/src/SmartFormat.Extensions.Time/Utilities/FileName.cs b/src/SmartFormat.Extensions.Time/Utilities/FileName.cs
new file mode 100644
index 00000000..0aafde41
--- /dev/null
+++ b/src/SmartFormat.Extensions.Time/Utilities/FileName.cs
@@ -0,0 +1,24 @@
+//
+// Copyright SmartFormat Project maintainers and contributors.
+// Licensed under the MIT license.
+//
+
+namespace SmartFormat.Extensions.Time.Utilities;
+internal struct TimeTextInfoData
+{
+ public string PluralRule { get; set; }
+ public string[] Ptxt_week { get; set; }
+ public string[] Ptxt_day { get; set; }
+ public string[] Ptxt_hour { get; set; }
+ public string[] Ptxt_minute { get; set; }
+ public string[] Ptxt_second { get; set; }
+ public string[] Ptxt_millisecond { get; set; }
+ public string[] Ptxt_w { get; set; }
+ public string[] Ptxt_d { get; set; }
+ public string[] Ptxt_h { get; set; }
+ public string[] Ptxt_m { get; set; }
+ public string[] Ptxt_s { get; set; }
+ public string[] Ptxt_ms { get; set; }
+ public string Ptxt_lessThan { get; set; }
+}
+
diff --git a/src/SmartFormat.Extensions.Time/Utilities/TimeSpanUtility.cs b/src/SmartFormat.Extensions.Time/Utilities/TimeSpanUtility.cs
index 90ed0281..32dfb8b8 100644
--- a/src/SmartFormat.Extensions.Time/Utilities/TimeSpanUtility.cs
+++ b/src/SmartFormat.Extensions.Time/Utilities/TimeSpanUtility.cs
@@ -4,8 +4,8 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
-using System.Text;
namespace SmartFormat.Extensions.Time.Utilities;
@@ -63,7 +63,7 @@ public static string ToTimeString(this TimeSpan fromTime, TimeSpanFormatOptions
/// These will be combined with the default timeSpanFormatOptions.
///
/// An object that supplies the text to use for output
- public static IList ToTimeParts(this TimeSpan fromTime, TimeSpanFormatOptions options,
+ internal static IList ToTimeParts(this TimeSpan fromTime, TimeSpanFormatOptions options,
TimeTextInfo timeTextInfo)
{
// If there are any missing options, merge with the defaults:
diff --git a/src/SmartFormat.Tests/Extensions.Time/TimeFormatterTests.cs b/src/SmartFormat.Tests/Extensions.Time/TimeFormatterTests.cs
index da23e875..5c02c272 100644
--- a/src/SmartFormat.Tests/Extensions.Time/TimeFormatterTests.cs
+++ b/src/SmartFormat.Tests/Extensions.Time/TimeFormatterTests.cs
@@ -5,7 +5,6 @@
using SmartFormat.Core.Settings;
using SmartFormat.Extensions;
using SmartFormat.Extensions.Time.Utilities;
-using SmartFormat.Tests.TestUtils;
using SmartFormat.Utilities;
namespace SmartFormat.Tests.Extensions;
@@ -42,7 +41,7 @@ private static object[] GetArgs()
}
[Test]
- public void UseTimeFormatter_WithIllegalLanguage()
+ public void UseTimeFormatter_WithUnsupportedLanguage()
{
var smart = GetFormatter();
var timeFormatter = smart.GetFormatterExtension()!;
@@ -72,35 +71,42 @@ public void Setting_Unknown_FallbackLanguage_Should_Throw()
Assert.That(ex, Is.InstanceOf().And.Message.Contains(nameof(TimeTextInfo)));
}
+ [Test]
+ public void UseTimeFormatter_WithSupportedlLanguage()
+ {
+ var smart = GetFormatter();
+
+ Assert.DoesNotThrow(() => smart.Format("{0:time(en):noless}", new TimeSpan(1,2,3)));
+ Assert.DoesNotThrow(() => smart.Format(CultureInfo.GetCultureInfo("en"), "{0:time:noless}", new TimeSpan(1,2,3)));
+ }
+
[TestCase(true)]
[TestCase(false)]
- public void UseTimeFormatter_With_Unimplemented_Language(bool useFallbackLanguage)
+ public void UseTimeFormatter_With_Unimplemented_FallbackLanguage(bool useFallbackLanguage)
{
var smart = GetFormatter();
var timeFormatter = smart.GetFormatterExtension()!;
timeFormatter.FallbackLanguage = useFallbackLanguage ? "en" : string.Empty;
- if(useFallbackLanguage)
- Assert.That(() => smart.Format("{0:time(nl):noless}", new TimeSpan(1,2,3)), Throws.Nothing);
+ if (useFallbackLanguage)
+ Assert.That(() => smart.Format("{0:time(nl):noless}", new TimeSpan(1, 2, 3)), Throws.Nothing);
else
- Assert.That(() => smart.Format("{0:time(nl):noless}", new TimeSpan(1,2,3)), Throws.TypeOf().And.Message.Contains(nameof(TimeTextInfo)));
+ Assert.That(() => smart.Format("{0:time(nl):noless}", new TimeSpan(1, 2, 3)), Throws.TypeOf().And.Message.Contains(nameof(TimeTextInfo)));
}
-
[Test]
- public void UseTimeFormatter_WithLegalLanguage()
+ public void Explicit_Formatter_With_UnsupportedArgType_Should_Throw()
{
var smart = GetFormatter();
-
- Assert.DoesNotThrow(() => smart.Format("{0:time(en):noless}", new TimeSpan(1,2,3)));
- Assert.DoesNotThrow(() => smart.Format(CultureInfo.GetCultureInfo("en"), "{0:time:noless}", new TimeSpan(1,2,3)));
+ // Arrays are not supported
+ Assert.That(() => smart.Format("{0:time:}", Array.Empty()), Throws.Exception.TypeOf());
}
[Test]
- public void Explicit_Formatter_With_Unsupported_ArgType_Should_Throw()
+ public void Formatter_ReturnsFalse_For_Implicit_Invocation()
{
- var smart = GetFormatter();
- Assert.That(() => smart.Format("{0:time:}", DateTime.UtcNow), Throws.Exception.TypeOf());
+ var tf = new TimeFormatter();
+ Assert.That(tf.TryEvaluateFormat(new FormattingInfo()), Is.False);
}
[TestCase("{0:time: {:list:|, | and }}", "en", "less than 1 second")]
@@ -171,6 +177,28 @@ public void Test_Options(string format, string expected)
Assert.That(actual, Is.EqualTo(expected));
}
+ [Test]
+ public void TimeSpanRoundedToString()
+ {
+ var tsInfo = CommonLanguagesTimeTextInfo.GetTimeTextInfo("en")!;
+ var result = new TimeSpan(0, 23, 0, 0)
+ .Round(TimeSpan.FromDays(1).Ticks).ToTimeString(TimeSpanFormatOptions.None, tsInfo);
+
+ Assert.That(result, Is.EqualTo("1 day"));
+ }
+
+#if NET6_0_OR_GREATER
+ [Test]
+ public void TimeOnlyArgument()
+ {
+ var args = GetArgs();
+ var smart = GetFormatter();
+
+ var actual = smart.Format("{0:time(en):noless}", new TimeOnly(13, 12, 11));
+ Assert.That(actual, Is.EqualTo("13 hours 12 minutes 11 seconds"));
+ }
+#endif
+
[TestCase(0)]
[TestCase(12)]
[TestCase(23)]
@@ -201,6 +229,32 @@ public void TimeSpanFromGivenTimeToCurrentTime(int diffHours)
SystemTime.ResetDateTime();
}
+ [Test]
+ public void DateTimeOffset_As_Argument_Should_FormatAsTimeSpan_ToLocaltime()
+ {
+ var smart = GetFormatter();
+ var dateTimeOffset = new DateTimeOffset(2025, 1, 1, 12, 0, 0, TimeSpan.Zero);
+ SystemTime.SetDateTimeOffset(dateTimeOffset.AddDays(1));
+ var format = "{0:time(en):abbr hours noless:}";
+
+ var actual = smart.Format(format, dateTimeOffset);
+ Assert.That(actual, Is.EqualTo("24h"));
+ SystemTime.ResetDateTime();
+ }
+
+ [Test]
+ public void DateTime_As_Argument_Should_FormatAsTimeSpan_ToLocaltime()
+ {
+ var smart = GetFormatter();
+ var dateTime = new DateTime(2025, 1, 1, 12, 0, 0, DateTimeKind.Unspecified);
+ SystemTime.SetDateTime(dateTime.AddDays(5));
+ var format = "{0:time(en):abbr days noless:}";
+
+ var actual = smart.Format(format, dateTime);
+ Assert.That(actual, Is.EqualTo("5d"));
+ SystemTime.ResetDateTime();
+ }
+
[TestCase(0)]
[TestCase(12)]
[TestCase(23)]