/
Month.cs
327 lines (283 loc) · 11.6 KB
/
Month.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
using Qowaiv.Globalization;
namespace Qowaiv;
/// <summary>Represents a month.</summary>
[DebuggerDisplay("{DebuggerDisplay}")]
[Serializable]
[SingleValueObject(SingleValueStaticOptions.All, typeof(byte))]
[OpenApiDataType(description: "Month(-only) notation.", example: "Jun", type: "string", format: "month", nullable: true, @enum: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec,?")]
[TypeConverter(typeof(MonthTypeConverter))]
#if NET6_0_OR_GREATER
[System.Text.Json.Serialization.JsonConverter(typeof(Json.MonthJsonConverter))]
#endif
public readonly partial struct Month : IXmlSerializable, IFormattable, IEquatable<Month>, IComparable, IComparable<Month>
{
/// <summary>Represents an unknown (but set) month.</summary>
public static Month Unknown => new(byte.MaxValue);
/// <summary>Represents January (01).</summary>
public static Month January /*...*/ => new(01);
/// <summary>Represents February (02).</summary>
public static Month February /*..*/ => new(02);
/// <summary>Represents March (03).</summary>
public static Month March /*.....*/ => new(03);
/// <summary>Represents April (04).</summary>
public static Month April /*.....*/ => new(04);
/// <summary>Represents May (05).</summary>
public static Month May /*.......*/ => new(05);
/// <summary>Represents June (06).</summary>
public static Month June /*......*/ => new(06);
/// <summary>Represents July (07).</summary>
public static Month July /*......*/ => new(07);
/// <summary>Represents August (08).</summary>
public static Month August /*....*/ => new(08);
/// <summary>Represents September (09).</summary>
public static Month September /*.*/ => new(09);
/// <summary>Represents October (10).</summary>
public static Month October /*..*/ => new(10);
/// <summary>Represents November (11).</summary>
public static Month November /*..*/ => new(11);
/// <summary>Represents December (12).</summary>
public static Month December /*..*/ => new(12);
/// <summary>Represents all months (January till December).</summary>
public static IReadOnlyList<Month> All { get; } =
[
January,
February,
March,
April,
May,
June,
July,
August,
September,
October,
November,
December,
];
/// <summary>Gets the full name of the month.</summary>
public string FullName => GetFullName(formatProvider: null);
/// <summary>Gets the short name of the month.</summary>
public string ShortName => GetShortName(formatProvider: null);
/// <summary>Gets the full name of the month.</summary>
[Pure]
public string GetFullName(IFormatProvider? formatProvider)
=> IsEmptyOrUnknown()
? ToDefaultString()
: (formatProvider as CultureInfo ?? CultureInfo.CurrentCulture).DateTimeFormat.GetMonthName(m_Value);
/// <summary>Gets the short name of the month.</summary>
[Pure]
public string GetShortName(IFormatProvider? formatProvider)
=> IsEmptyOrUnknown()
? ToDefaultString()
: (formatProvider as CultureInfo ?? CultureInfo.CurrentCulture).DateTimeFormat.GetAbbreviatedMonthName(m_Value);
/// <summary>Returns the number of days for the month.</summary>
/// <param name="year">
/// The year to ask the number of days for.
/// </param>
/// <remarks>
/// If the year of month is empty or unknown -1 is returned.
/// </remarks>
[Pure]
public int Days(Year year)
=> year.IsEmptyOrUnknown() || IsEmptyOrUnknown()
? -1
: DateTime.DaysInMonth((int)year, m_Value);
/// <summary>Deserializes the month from a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
/// </param>
/// <returns>
/// The deserialized month.
/// </returns>
[Pure]
public static Month FromJson(double json) => Create((int)json);
/// <summary>Deserializes the month from a JSON number.</summary>
/// <param name="json">
/// The JSON number to deserialize.
/// </param>
/// <returns>
/// The deserialized month.
/// </returns>
[Pure]
public static Month FromJson(long json) => Create((int)json);
/// <summary>Serializes the month to a JSON node.</summary>
/// <returns>
/// The serialized JSON string.
/// </returns>
[Pure]
public string? ToJson() => m_Value == default ? null : ToString("s", CultureInfo.InvariantCulture);
/// <summary>Returns a <see cref="string"/> that represents the current month for debug purposes.</summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => this.DebuggerDisplay("{0:f (m)}");
/// <summary>Returns a formatted <see cref="string"/> that represents the current month.</summary>
/// <param name="format">
/// The format that describes the formatting.
/// </param>
/// <param name="formatProvider">
/// The format provider.
/// </param>
/// <remarks>
/// The formats:
///
/// f: as full name.
/// s: as short name.
/// M: as number with leading zero.
/// m: as number without leading zero.
/// </remarks>
[Pure]
public string ToString(string? format, IFormatProvider? formatProvider)
=> StringFormatter.TryApplyCustomFormatter(format, this, formatProvider, out string formatted)
? formatted
: StringFormatter.Apply(this, format.WithDefault("f"), formatProvider, FormatTokens);
/// <summary>Gets an XML string representation of the month.</summary>
[Pure]
private string ToXmlString() => ToString("s", CultureInfo.InvariantCulture);
[Pure]
private string ToDefaultString() => IsUnknown() ? "?" : string.Empty;
/// <summary>The format token instructions.</summary>
private static readonly Dictionary<char, Func<Month, IFormatProvider, string>> FormatTokens = new()
{
{ 'f', (svo, provider) => svo.GetFullName(provider) },
{ 's', (svo, provider) => svo.GetShortName(provider) },
{ 'M', (svo, provider) => svo.IsEmptyOrUnknown() ? svo.ToDefaultString() : svo.m_Value.ToString("0", provider) },
{ 'm', (svo, provider) => svo.IsEmptyOrUnknown() ? svo.ToDefaultString() : svo.m_Value.ToString("00", provider) },
};
/// <summary>Casts a month to a System.Int32.</summary>
public static explicit operator int(Month val) => val.m_Value;
/// <summary>Casts an System.Int32 to a month.</summary>
public static explicit operator Month(int val) => Cast.Primitive<int, Month>(TryCreate, val);
/// <summary>Returns true if the left operator is less then the right operator, otherwise false.</summary>
public static bool operator <(Month l, Month r) => HaveValue(l, r) && l.CompareTo(r) < 0;
/// <summary>Returns true if the left operator is greater then the right operator, otherwise false.</summary>
public static bool operator >(Month l, Month r) => HaveValue(l, r) && l.CompareTo(r) > 0;
/// <summary>Returns true if the left operator is less then or equal the right operator, otherwise false.</summary>
public static bool operator <=(Month l, Month r) => HaveValue(l, r) && l.CompareTo(r) <= 0;
/// <summary>Returns true if the left operator is greater then or equal the right operator, otherwise false.</summary>
public static bool operator >=(Month l, Month r) => HaveValue(l, r) && l.CompareTo(r) >= 0;
[Pure]
private static bool HaveValue(Month l, Month r) => !l.IsEmptyOrUnknown() && !r.IsEmptyOrUnknown();
/// <summary>Converts the string to a month.
/// A return value indicates whether the conversion succeeded.
/// </summary>
/// <param name="s">
/// A string containing a month to convert.
/// </param>
/// <param name="provider">
/// The specified format provider.
/// </param>
/// <param name="result">
/// The result of the parsing.
/// </param>
/// <returns>
/// True if the string was converted successfully, otherwise false.
/// </returns>
public static bool TryParse(string? s, IFormatProvider? provider, out Month result)
{
result = default;
var str = s.Unify();
if (str.IsEmpty())
{
return true;
}
else if (str.IsUnknown(provider))
{
result = Unknown;
return true;
}
else if (byte.TryParse(s, NumberStyles.None, provider, out var n) && IsValid(n))
{
result = new Month(n);
return true;
}
else if (ParseValues.TryGetValue(provider, str, out byte m))
{
result = new Month(m);
return true;
}
else return false;
}
/// <summary>Creates a month from a Byte.</summary>
/// <param name="val" >
/// A decimal describing a month.
/// </param >
/// <exception cref="FormatException">
/// val is not a valid month.
/// </exception>
[Pure]
public static Month Create(int? val)
=> TryCreate(val, out Month result)
? result
: throw new ArgumentOutOfRangeException(nameof(val), QowaivMessages.FormatExceptionMonth);
/// <summary>Creates a month from a Byte.
/// A return value indicates whether the conversion succeeded.
/// </summary >
/// <param name="val" >
/// A decimal describing a month.
/// </param >
/// <returns >
/// A month if the creation was successfully, otherwise Month.Empty.
/// </returns >
[Pure]
public static Month TryCreate(int? val)
=> TryCreate(val, out Month result)
? result
: default;
/// <summary>Creates a month from a Byte.
/// A return value indicates whether the creation succeeded.
/// </summary >
/// <param name="val" >
/// A Byte describing a month.
/// </param >
/// <param name="result" >
/// The result of the creation.
/// </param >
/// <returns >
/// True if a month was created successfully, otherwise false.
/// </returns >
public static bool TryCreate(int? val, out Month result)
{
result = Empty;
if (!val.HasValue) { return true; }
else if (IsValid(val.Value))
{
result = new Month((byte)val.Value);
return true;
}
else { return false; }
}
/// <summary>Returns true if the val represents a valid month, otherwise false.</summary>
[Pure]
private static bool IsValid(int? val)
=> val.HasValue
&& val >= January.m_Value
&& val <= December.m_Value;
private static readonly MonthValues ParseValues = new();
private sealed class MonthValues : LocalizedValues<byte>
{
public MonthValues() : base(new()
{
{ "JAN", 01 }, { "JANUARY", 1 },
{ "FEB", 02 }, { "FEBRUARY", 2 },
{ "MAR", 03 }, { "MARCH", 3 },
{ "APR", 04 }, { "APRIL", 4 },
{ "MAY", 05 },
{ "JUN", 06 }, { "JUNE", 6 },
{ "JUL", 07 }, { "JULY", 7 },
{ "AUG", 08 }, { "AUGUST", 8 },
{ "SEP", 09 }, { "SEPTEMBER", 9 },
{ "OCT", 10 }, { "OCTOBER", 10 },
{ "NOV", 11 }, { "NOVEMBER", 11 },
{ "DEC", 12 }, { "DECEMBER", 12 },
}) { }
protected override void AddCulture(CultureInfo culture)
{
foreach (var month in All)
{
var m = (byte)month;
var full = culture.DateTimeFormat.GetAbbreviatedMonthName(m).Unify();
var shrt = culture.DateTimeFormat.GetMonthName(m).Unify();
this[culture][full] = m;
this[culture][shrt] = m;
}
}
}
}