Created
July 3, 2022 10:40
-
-
Save iamr8/ac7670e904418c7c859b719fca017011 to your computer and use it in GitHub Desktop.
A struct which works like DateTime.cs to have a conversion to Persian Calendar
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Represents an instance of <see cref="PersianDateTime"/> | |
/// </summary> | |
public struct PersianDateTime | |
{ | |
/// <summary> | |
/// Gets the hour component of the date by this instance | |
/// </summary> | |
/// <returns>The hour component, expressed as a value between 0 and 23</returns> | |
public int Hour { get; } | |
/// <summary> | |
/// Gets the minute component of the date by this instance | |
/// </summary> | |
/// <returns>The minute component, expressed as a value between 0 and 59</returns> | |
public int Minute { get; } | |
/// <summary> | |
/// Gets the year component of the date by this instance | |
/// </summary> | |
/// <returns>The year between 1 and 9999</returns> | |
public int Year { get; } | |
/// <summary> | |
/// Gets the month component of the date by this instance | |
/// </summary> | |
/// <returns>The month component, expressed as a value between 1 and 12</returns> | |
public int Month { get; } | |
/// <summary> | |
/// Gets the second component of the date by this instance | |
/// </summary> | |
/// <returns>The second component, expressed as a value between 0 and 59</returns> | |
public int Second { get; } | |
/// <summary> | |
/// Gets the day component of the date by this instance | |
/// </summary> | |
/// <returns>The day component, expressed as a value between 1 and 31 </returns> | |
public int Day { get; } | |
/// <summary> | |
/// Gets the day of the week represented by this instance | |
/// </summary> | |
/// <returns>An enumerated constant that indicates the day of the week by this <see cref="PersianDateTime"/> value.</returns> | |
public DayOfWeek DayOfWeek { get; } | |
/// <summary> | |
/// Represents an instance of <see cref="PersianDateTime"/> for <c>DateTime.Now</c> | |
/// </summary> | |
public static PersianDateTime Now => GetFromDateTime(DateTime.Now); | |
private static bool TryParseTime(string time, out int? hour, out int? minute, out int? second) | |
{ | |
hour = null; | |
minute = null; | |
second = null; | |
var parts = time.Split(new[] { ":" }, StringSplitOptions.None).ToList(); | |
if (!parts.Any()) | |
return false; | |
var list = new List<int>(); | |
foreach (var part in parts) | |
{ | |
var parse = int.TryParse(part, out var num); | |
if (!parse) | |
return false; | |
list.Add(num); | |
} | |
if (list.Any(x => x < 0)) | |
return false; | |
switch (parts.Count) | |
{ | |
case 3: | |
hour = list[0]; | |
minute = list[1]; | |
second = list[2]; | |
return true; | |
case 2: | |
hour = list[0]; | |
minute = list[1]; | |
second = 0; | |
return true; | |
default: | |
return false; | |
} | |
} | |
private static bool TryParseDate(string date, out int? year, out int? month, out int? day) | |
{ | |
year = null; | |
month = null; | |
day = null; | |
var parts = date.Split(new[] { "/", "-" }, StringSplitOptions.None).ToList(); | |
if (parts.Count == 0) | |
return false; | |
var list = new List<int>(); | |
foreach (var part in parts) | |
{ | |
var parse = int.TryParse(part, out var num); | |
if (!parse) | |
return false; | |
list.Add(num); | |
} | |
if (list.Any(x => x <= 0)) | |
return false; | |
var yearIndex = 0; | |
while (yearIndex <= 2) | |
{ | |
var tempYear = list[yearIndex]; | |
if (tempYear >= 1300) | |
{ | |
year = tempYear; | |
break; | |
} | |
yearIndex++; | |
} | |
if (year == null) | |
return false; | |
list.RemoveAt(yearIndex); | |
if (list.All(x => x <= 12)) | |
{ | |
month = list[0]; | |
day = list[1]; | |
} | |
else | |
{ | |
day = list.First(x => x > 12); | |
month = list.First(x => x <= 12); | |
} | |
return true; | |
} | |
/// <summary> | |
/// Converts the string representation of an <see cref="PersianDateTime"/> instance. | |
/// </summary> | |
/// <param name="s">An <see cref="string"/> containing Persian date time format</param> | |
/// <param name="value">A <see cref="PersianDateTime"/> instance that represents parsed string to instance.</param> | |
/// <returns>A <see cref="bool"/> that represents if string parsed to valid instance.</returns> | |
/// <remarks>If true, string fully parsed, not parsed otherwise.</remarks> | |
public static bool TryParse(string s, out PersianDateTime? value) | |
{ | |
value = null; | |
try | |
{ | |
value = Parse(s); | |
return true; | |
} | |
catch (Exception e) | |
{ | |
return false; | |
} | |
} | |
/// <summary> | |
/// Converts the string representation of an <see cref="PersianDateTime"/> instance. | |
/// </summary> | |
/// <param name="s">An <see cref="string"/> containing Persian date time format</param> | |
/// <exception cref="ArgumentException"></exception> | |
/// <exception cref="ArgumentOutOfRangeException"></exception> | |
/// <exception cref="ArgumentNullException"></exception> | |
/// <returns>A <see cref="PersianDateTime"/> instance</returns> | |
public static PersianDateTime Parse(string s) | |
{ | |
if (s == null) | |
throw new ArgumentNullException(nameof(s)); | |
var sectionSplit = new[] { " " }; | |
var splitText = s.Split(sectionSplit, StringSplitOptions.None).ToList(); | |
switch (splitText.Count) | |
{ | |
case 1: | |
{ | |
var date = splitText[0]; | |
var tryParseDate = TryParseDate(date, out var year, out var month, out var day); | |
if (tryParseDate) | |
return new PersianDateTime(year.Value, month.Value, day.Value, 0, 0, 1); | |
var tryParseTime = TryParseTime(date, out var hour, out var minute, out var second); | |
if (tryParseTime) | |
return new PersianDateTime(Now.Year, Now.Month, Now.Day, hour.Value, minute.Value, second.Value); | |
throw new ArgumentException($"{date} is not in expected format."); | |
} | |
case 2: | |
default: | |
{ | |
var date = splitText[0]; | |
var tryParseDate = TryParseDate(date, out var year, out var month, out var day); | |
if (!tryParseDate) | |
throw new ArgumentException($"{date} is not in expected format for date."); | |
var time = splitText[1]; | |
var tryParseTime = TryParseTime(time, out var hour, out var minute, out var second); | |
if (!tryParseTime) | |
throw new ArgumentException($"{date} is not in expected format for time."); | |
return new PersianDateTime(year.Value, month.Value, day.Value, hour.Value, minute.Value, second.Value); | |
} | |
} | |
} | |
/// <summary> | |
/// Initializes an instance of <see cref="PersianDateTime"/> | |
/// </summary> | |
/// <param name="dateTime">An initiated <see cref="DateTime"/></param> | |
/// <exception cref="ArgumentOutOfRangeException"></exception> | |
public static PersianDateTime GetFromDateTime(DateTime dateTime) | |
{ | |
var persianCalendar = new PersianCalendar(); | |
var year = persianCalendar.GetYear(dateTime); | |
var month = persianCalendar.GetMonth(dateTime); | |
var day = persianCalendar.GetDayOfMonth(dateTime); | |
var dayOfWeek = persianCalendar.GetDayOfWeek(dateTime); | |
var hour = dateTime.Hour; | |
var minute = dateTime.Minute; | |
var second = dateTime.Second; | |
var instance = new PersianDateTime(year, month, day, hour, minute, second, dayOfWeek, false); | |
return instance; | |
} | |
/// <summary> | |
/// Initializes an instance of <see cref="PersianDateTime"/> | |
/// </summary> | |
/// <param name="year">Specific persian year</param> | |
/// <param name="month">Specific persian month</param> | |
/// <param name="day">Specific persian day</param> | |
public PersianDateTime(int year, int month, int day) : this(year, month, day, 0, 0, 1) | |
{ | |
} | |
/// <summary> | |
/// Initializes an instance of <see cref="PersianDateTime"/> | |
/// </summary> | |
/// <param name="year">Specific year</param> | |
/// <param name="month">Specific month</param> | |
/// <param name="day">Specific day</param> | |
/// <param name="hour">Specific hour</param> | |
/// <param name="minute">Specific minute</param> | |
/// <param name="second">Specific second</param> | |
/// <param name="dayOfWeek"></param> | |
/// <param name="gregorianDate">Set if parameters entered as <see cref="DateTime"/> components</param> | |
internal PersianDateTime(int year, int month, int day, int hour, int minute, int second, DayOfWeek? dayOfWeek, bool gregorianDate) | |
{ | |
if (gregorianDate) | |
{ | |
var dt = new DateTime(year, month, day, hour, minute, second); | |
var pDt = GetFromDateTime(dt); | |
this.Year = pDt.Year; | |
this.Month = pDt.Month; | |
this.Day = pDt.Day; | |
this.Hour = pDt.Hour; | |
this.Minute = pDt.Minute; | |
this.Second = pDt.Second; | |
this.DayOfWeek = pDt.DayOfWeek; | |
} | |
else | |
{ | |
this.Year = year; | |
this.Month = month; | |
this.Day = day; | |
this.Hour = hour; | |
this.Minute = minute; | |
this.Second = second; | |
if (dayOfWeek == null) | |
{ | |
var dt = new DateTime(year, month, day, hour, minute, second, new PersianCalendar()); | |
this.DayOfWeek = dt.DayOfWeek; | |
} | |
else | |
{ | |
this.DayOfWeek = dayOfWeek.Value; | |
} | |
} | |
} | |
/// <summary> | |
/// Initializes an instance of <see cref="PersianDateTime"/> | |
/// </summary> | |
/// <param name="year">Specific persian year</param> | |
/// <param name="month">Specific persian month</param> | |
/// <param name="day">Specific persian day</param> | |
/// <param name="hour">Specific hour</param> | |
/// <param name="minute">Specific minute</param> | |
/// <param name="second">Specific second</param> | |
public PersianDateTime(int year, int month, int day, int hour, int minute, int second) : this(year, month, day, hour, minute, second, null, false) | |
{ | |
} | |
/// <summary> | |
/// Represents days of specific month | |
/// </summary> | |
/// <returns>Days count</returns> | |
/// <exception cref="ArgumentOutOfRangeException"></exception> | |
public int GetMonthDaysCount() => GetMonthDaysCount(Month); | |
/// <summary> | |
/// Represents days of specific month | |
/// </summary> | |
/// <param name="month">Month number</param> | |
/// <returns>Days count</returns> | |
/// <exception cref="ArgumentOutOfRangeException"></exception> | |
public static int GetMonthDaysCount(int month) | |
{ | |
if (month >= 1 && month <= 6) | |
return 31; | |
if (month >= 7 && month <= 11) | |
return 30; | |
if (month == 12) | |
return 29; | |
throw new ArgumentOutOfRangeException($"{nameof(month)} should be in range between 1 and 12"); | |
} | |
/// <summary> | |
/// Converts current <see cref="PersianDateTime"/> instance to <see cref="DateTime"/> | |
/// </summary> | |
/// <returns>An instance of <see cref="DateTime"/></returns> | |
public DateTime ToDateTime() => new DateTime(Year, Month, Day, Hour, Minute, Second, new PersianCalendar()); | |
/// <summary> | |
/// Represents Humanized text for this Persian DateTime | |
/// </summary> | |
/// <returns>For example : 10 ثانیه پیش</returns> | |
/// <exception cref="ArgumentOutOfRangeException"></exception> | |
public string Humanize() | |
{ | |
var now = DateTime.UtcNow; | |
var dt = ToDateTime().ToUniversalTime(); | |
var nowSeconds = new DateTimeOffset(now).ToUnixTimeSeconds(); | |
var dtSeconds = new DateTimeOffset(dt).ToUnixTimeSeconds(); | |
var division = nowSeconds - dtSeconds; | |
if (division < 0) | |
throw new ArgumentOutOfRangeException("Desired date should be lower than today"); | |
var timeSpan = TimeSpan.FromSeconds(division); | |
if (timeSpan.TotalSeconds < TimeSpan.FromMinutes(1).TotalSeconds) // زیر یک دقیقه | |
return $"{division} ثانیه پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromMinutes(1).TotalSeconds && | |
timeSpan.TotalSeconds < TimeSpan.FromMinutes(10).TotalSeconds) // بین 1 تا 10 دقیقه | |
return $"{(int)timeSpan.TotalMinutes} دقیقه پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromMinutes(10).TotalSeconds && timeSpan.TotalSeconds < TimeSpan.FromMinutes(30).TotalSeconds) | |
return $"{((int)timeSpan.TotalMinutes % 5 == 0 ? (int)timeSpan.TotalMinutes : Math.Round(timeSpan.TotalMinutes / 5, 0) * 5)} دقیقه پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromMinutes(30).TotalSeconds && timeSpan.TotalSeconds < TimeSpan.FromMinutes(45).TotalSeconds) | |
return "نیم ساعت پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromMinutes(45).TotalSeconds && timeSpan.TotalSeconds < TimeSpan.FromHours(1).TotalSeconds) // ده دقیقه به بالا | |
return "کمتر از یک ساعت پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromHours(1).TotalSeconds && timeSpan.TotalSeconds < TimeSpan.FromDays(1).TotalSeconds) | |
return $"{(int)timeSpan.TotalHours} ساعت پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromDays(1).TotalSeconds && timeSpan.TotalSeconds < TimeSpan.FromDays(7).TotalSeconds) | |
return $"{(int)timeSpan.TotalDays} روز پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromDays(7).TotalSeconds && timeSpan.TotalSeconds < TimeSpan.FromDays(30).TotalSeconds) | |
return $"{(int)timeSpan.TotalDays / 7} هفته پیش"; | |
if (timeSpan.TotalSeconds >= TimeSpan.FromDays(30).TotalSeconds && timeSpan.TotalSeconds < TimeSpan.FromDays(30 * 12).TotalSeconds) | |
return $"{(int)timeSpan.TotalDays / 30} ماه پیش"; | |
return $"{(int)timeSpan.TotalDays / (12 * 30)} سال پیش"; | |
} | |
public override string ToString() | |
{ | |
return ToString(true, true); | |
} | |
/// <summary> | |
/// Represents Persian month name | |
/// </summary> | |
/// <exception cref="ArgumentOutOfRangeException"></exception> | |
/// <returns>An <see cref="string"/> value that representing persian month name</returns> | |
public string GetMonthName() => GetMonthName(Month); | |
/// <summary> | |
/// Represents Persian month name | |
/// </summary> | |
/// <param name="month">Persian month number</param> | |
/// <exception cref="ArgumentOutOfRangeException"></exception> | |
/// <returns>An <see cref="string"/> value that representing persian month name</returns> | |
public static string GetMonthName(int month) | |
{ | |
if (month <= 0 || month > 12) | |
throw new ArgumentOutOfRangeException($"{month} must be a value between 1 and 12"); | |
return month switch | |
{ | |
1 => "فروردین", | |
2 => "اردیبهشت", | |
3 => "خرداد", | |
4 => "تیر", | |
5 => "مرداد", | |
6 => "شهریور", | |
7 => "مهر", | |
8 => "آبان", | |
9 => "آذر", | |
10 => "دی", | |
11 => "بهمن", | |
12 => "اسفند", | |
_ => null | |
}; | |
} | |
/// <summary> | |
/// Gets the day of the week based on <see cref="DayOfWeek"/> | |
/// </summary> | |
/// <returns>An translated string of enumerated constant ( such as : <c>جمعه</c> )</returns> | |
public string GetDayOfWeek() => GetDayOfWeek(DayOfWeek); | |
/// <summary> | |
/// Gets the day of the week based on <see cref="DayOfWeek"/> | |
/// </summary> | |
/// <param name="dayOfWeek">A enumerator constant that representing day of week</param> | |
/// <returns>An translated string of enumerated constant ( such as : <c>جمعه</c> )</returns> | |
public static string GetDayOfWeek(DayOfWeek dayOfWeek) => | |
dayOfWeek switch | |
{ | |
DayOfWeek.Friday => "جمعه", | |
DayOfWeek.Monday => "دوشنبه", | |
DayOfWeek.Sunday => "یکشنبه", | |
DayOfWeek.Thursday => "پنجشنبه", | |
DayOfWeek.Tuesday => "سه شنبه", | |
DayOfWeek.Wednesday => "چهارشنبه", | |
DayOfWeek.Saturday => "شنبه", | |
_ => "شنبه" | |
}; | |
/// <summary> | |
/// Returns the fully qualified type name of this instance. | |
/// </summary> | |
/// <param name="showDayOfWeek">Show persian day of week.</param> | |
/// <returns>The fully qualified type name.</returns> | |
public string ToString(bool showDayOfWeek) | |
{ | |
var date = $"{Day} {GetMonthName()} {Year}"; | |
return showDayOfWeek | |
? $"{GetDayOfWeek()}، {date}" | |
: date; | |
} | |
/// <summary> | |
/// Returns the fully qualified type name of this instance. | |
/// </summary> | |
/// <param name="showTime">Show time</param> | |
/// <param name="showTimeSecond">time second component in time</param> | |
/// <returns>The fully qualified type name.</returns> | |
public string ToString(bool showTime, bool showTimeSecond) | |
{ | |
var date = $"{Year:0000}/{Month:00}/{Day:00}"; | |
var time = $"{Hour:00}:{Minute:00}"; | |
if (showTimeSecond) | |
time += $":{Second:00}"; | |
return showTime | |
? $"{date} {time}" | |
: $"{date}"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment