Files
Common-VS2019/SacsStandardBase/Base/Utils/DateUtil.cs
T
2025-11-20 17:17:40 +00:00

689 lines
23 KiB
C#

using Sarand.Net.Standard.Base.App;
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Sarand.Net.Standard.Base.Utils
{
//=======================================================================
public static class DateUtil
{
//-------------------------------------------------------------------------
// Definitions.
public static String Format_SqlDate { get; } = "yyyy-MM-dd";
public static String Format_SqlTime { get; } = "HH:mm:ss.fff";
public static String Delim_SqlTime { get; } = " ";
public static String Format_SqlDateTime { get; } = Format_SqlDate + Delim_SqlTime + Format_SqlTime;
public static String Format_XmlDate { get; } = "yyyy-MM-dd";
public static String Format_XmlTime { get; } = "HH:mm:ss";
public static String Delim_XmlTime { get; } = "T";
public static String Format_XmlDateTime { get; } = Format_XmlDate + Delim_XmlTime + Format_XmlTime;
public static String Format_JavascriptDate { get; } = "yyyy-MM-dd";
public static String Format_JavascriptTime { get; } = "HH:mm:ss";
public static String Delim_JavascriptTime { get; } = "T";
//-------------------------------------------------------------------------
// P o p u l a t i o n
//-------------------------------------------------------------------------
/// <summary>Combine date and time.</summary>
public static DateTime SetTime(this DateTime value, DateTime time)
{
if (!IsValid(value)) return value;
return value.Date.Add(time.TimeOfDay);
}
/// <summary>Combine date and time.</summary>
public static DateTime SetTime(this DateTime value, TimeSpan time)
{
if (!IsValid(value)) return value;
return value.Date + time;
}
//-------------------------------------------------------------------------
public static DateTime ClearSeconds(this DateTime value)
{
return new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, 0, value.Kind);
}
//-------------------------------------------------------------------------
public enum RoundingDirection
{
/// <summary>Set to next minutes interval if past previous.</summary>
Up,
/// <summary>Set to previous minutes interval if not reached next.</summary>
Down,
/// <summary>Set to nearest minutes interval.</summary>
Nearest,
}
/// <summary>
/// Roud the time portion of the value to the appropriate multiple of the interval minutes.
/// <para>Seconds can be included in the algorythm but are cleared in the return value.</para>
/// <para>Interval minutes are capped at 60.</para>
/// </summary>
public static DateTime RoundTime(this DateTime value, ushort intervalMinutes, RoundingDirection direction, bool ignoreSeconds = true)
{
intervalMinutes = (ushort)Math.Min(60, (int)intervalMinutes);
// Number of minutes over the interval.
double minsDifference = (value.Minute % intervalMinutes) + (ignoreSeconds ? 0.0 : (value.Second / 60.0));
// Value rounded down to the interval minutes.
DateTime newValue = value.Date + new TimeSpan(value.Hour, value.Minute - (value.Minute % intervalMinutes), 0);
switch (direction)
{
case RoundingDirection.Up:
if (minsDifference > 0.0)
newValue += new TimeSpan(0, intervalMinutes, 0);
break;
case RoundingDirection.Down:
// Nothng to do.
break;
case RoundingDirection.Nearest:
if (minsDifference > (intervalMinutes / 2.0))
newValue += new TimeSpan(0, intervalMinutes, 0);
break;
}
return newValue;
}
//-------------------------------------------------------------------------
// C o n v e r t F r o m S t r i n g
//-------------------------------------------------------------------------
/// <summary>
/// Parse date value.
/// <para>Format: eg. 'yyyyMMdd', 'dd/MM/yyyy'</para>
/// </summary>
public static DateTime ParseExact(String value, String format, CultureInfo culture = null)
{
try
{
return DateTime.ParseExact(value, format, culture ?? CultureInfo.InvariantCulture, DateTimeStyles.None);
}
catch (Exception) { }
return DateTime.MinValue;
}
//-------------------------------------------------------------------------
/// <summary>Parse XML date: 'yyyy-MM-dd'</summary>
public static DateTime ParseXmlDate(String value)
{
return ParseExact(value, Format_XmlDate);
}
/// <summary>Parse XML time: 'HH:mm:ss'</summary>
public static DateTime ParseXmlTime(String value)
{
return ParseExact(value, Format_XmlTime);
}
/// <summary>Parse XML datetime: 'yyyy-MM-ddTHH:mm:ss'</summary>
public static DateTime ParseXmlDateTime(String value)
{
return ParseExact(value, Format_XmlDateTime);
}
//-------------------------------------------------------------------------
// V a l i d a t i o n
//-------------------------------------------------------------------------
/// <summary>Vaidate that date is between MinDate & MaxDate.</summary>
public static bool IsValid(this DateTime value)
{
if (value <= DateTime.MinValue) return false;
if (value >= DateTime.MaxValue) return false;
return true;
}
/// <summary>Vaidate that date is between MinDate & MaxDate and after null date.</summary>
public static bool IsValid(this DateTime value, DateTime nullDate)
{
if (!IsValid(value)) return false;
if (value <= nullDate) return false;
return true;
}
/// <summary>Vaidate that date is not null and between MinDate & MaxDate.</summary>
public static bool IsValid(this DateTime? value)
{
return (value != null && IsValid((DateTime)value));
}
//-------------------------------------------------------------------------
// I n v a l i d
//-------------------------------------------------------------------------
public static DateTime OnInvalid(this DateTime? value, DateTime defaultValue)
{
if (!IsValid(value)) return defaultValue;
return (DateTime)value;
}
//-------------------------------------------------------------------------
// C o m p a r i s o n
//-------------------------------------------------------------------------
public static DateTime Min(DateTime time1, DateTime time2)
{
return new DateTime(Math.Min(time1.Ticks, time2.Ticks), time1.Kind);
}
public static DateTime Max(DateTime time1, DateTime time2)
{
return new DateTime(Math.Max(time1.Ticks, time2.Ticks), time1.Kind);
}
//-------------------------------------------------------------------------
// P e r i o d s
//-------------------------------------------------------------------------
public enum Period
{
Period,
Day,
Today,
Yesterday,
Week,
WeekCurrent,
WeekPrevious,
WorkingWeek,
WorkingWeekCurrent,
WorkingWeekPrevious,
Month,
MonthCurrent,
MonthPrevious,
Quarter,
QuarterCurrent,
QuarterPrevious,
Year,
YearCurrent,
YearPrevious,
}
public static String GetPeriodCaption(Period period)
{
switch (period)
{
case Period.Period:
return "Period";
case Period.Day:
return "Day";
case Period.Today:
return "Today";
case Period.Yesterday:
return "Yesterday";
case Period.Week:
return "Week";
case Period.WeekCurrent:
return "This week";
case Period.WeekPrevious:
return "Last week";
case Period.WorkingWeek:
return "Working week";
case Period.WorkingWeekCurrent:
return "This working week";
case Period.WorkingWeekPrevious:
return "Last working week";
case Period.Month:
return "Month";
case Period.MonthCurrent:
return "This month";
case Period.MonthPrevious:
return "Last month";
case Period.Quarter:
return "Quarter";
case Period.QuarterCurrent:
return "This quarter";
case Period.QuarterPrevious:
return "Last quarter";
case Period.Year:
return "Year";
case Period.YearCurrent:
return "This year";
case Period.YearPrevious:
return "Last year";
}
return period.ToString();
}
public static bool IsPeriodFromFixed(Period period)
{
switch (period)
{
case Period.Period:
case Period.Day:
case Period.Week:
case Period.WorkingWeek:
case Period.Month:
case Period.Quarter:
case Period.Year:
return false;
}
return true;
}
public static bool IsPeriodToFixed(Period period)
{
switch (period)
{
case Period.Period:
return false;
}
return true;
}
public class ComboBoxItemPeriod
{
public DateUtil.Period Period { get; }
public ComboBoxItemPeriod(DateUtil.Period period)
{
Period = period;
}
public override String ToString()
{
return GetPeriodCaption(Period);
}
// Comparison.
public override int GetHashCode()
{
return 23 + Period.GetHashCode();
}
public override bool Equals(object obj)
{
if (obj is ComboBoxItemPeriod) return Equals((ComboBoxItemPeriod)obj);
if (obj is Period) return Equals((Period)obj);
return base.Equals(obj);
}
public bool Equals(ComboBoxItemPeriod item)
{
return (Period == item.Period);
}
public bool Equals(Period period)
{
return (Period == period);
}
}
public static DateTime StartOfPeriod(Period period, DateTime from, DateTime to)
{
switch (period)
{
case Period.Period:
return from;
case Period.Day:
return from;
case Period.Today:
return DateTime.Today;
case Period.Yesterday:
return DateTime.Today.AddDays(-1);
case Period.Week:
return from.StartOfWeek();
case Period.WeekCurrent:
return DateTime.Today.StartOfWeek();
case Period.WeekPrevious:
return DateTime.Today.StartOfWeek().AddDays(-7);
case Period.WorkingWeek:
return from.StartOfWorkingWeek();
case Period.WorkingWeekCurrent:
return DateTime.Today.StartOfWorkingWeek();
case Period.WorkingWeekPrevious:
return DateTime.Today.StartOfWorkingWeek().AddDays(-7);
case Period.Month:
return from.StartOfMonth();
case Period.MonthCurrent:
return DateTime.Today.StartOfMonth();
case Period.MonthPrevious:
return DateTime.Today.StartOfMonth().AddMonths(-1);
case Period.Quarter:
return from.StartOfQuarter();
case Period.QuarterCurrent:
return DateTime.Today.StartOfQuarter();
case Period.QuarterPrevious:
return DateTime.Today.StartOfQuarter().AddMonths(-3);
case Period.Year:
return from.StartOfYear();
case Period.YearCurrent:
return DateTime.Today.StartOfYear();
case Period.YearPrevious:
return DateTime.Today.StartOfYear().AddYears(-1);
}
return DateTime.Today;
}
public static DateTime EndOfPeriod(Period period, DateTime from, DateTime to)
{
DateTime startOfPeriod = StartOfPeriod(period, from, to);
switch (period)
{
case Period.Period:
if (to.CompareTo(from) < 0) return from;
return to;
case Period.Day:
case Period.Today:
case Period.Yesterday:
return startOfPeriod;
case Period.Week:
case Period.WeekCurrent:
case Period.WeekPrevious:
case Period.WorkingWeek:
case Period.WorkingWeekCurrent:
case Period.WorkingWeekPrevious:
return startOfPeriod.AddDays(6);
case Period.Month:
case Period.MonthCurrent:
case Period.MonthPrevious:
return startOfPeriod.AddMonths(1).AddDays(-1);
case Period.Quarter:
case Period.QuarterCurrent:
case Period.QuarterPrevious:
return startOfPeriod.AddMonths(3).AddDays(-1);
case Period.Year:
case Period.YearCurrent:
case Period.YearPrevious:
return startOfPeriod.AddYears(1).AddDays(-1);
}
return DateTime.Today;
}
//-------------------------------------------------------------------
// R e l a t i v e D a t e s
//-------------------------------------------------------------------
// Relative dates - day.
public static DateTime PreviousDay(this DateTime value)
{
return value.AddDays(-1);
}
public static DateTime NextDay(this DateTime value)
{
return value.AddDays(1);
}
public static DateTime IncrementDate(this DateTime value, int days)
{
return value.AddDays(days);
}
public static DateTime IncrementMonth(this DateTime value, int months)
{
return value.AddMonths(months);
}
//-------------------------------------------------------------------
// Relative dates - week.
/// <summary>
/// Sunday at start of week including specified date.
/// </summary>
/// <param name="value">Date in week for which starting Sunday is required</param>
/// <returns></returns>
public static DateTime StartOfWeek(this DateTime value)
{
// value.DayOfWeek is number of days to go back to Sunday (0 for Sunday, 1 for Monday etc).
return value.AddDays(-(int)value.DayOfWeek);
}
public static DateTime StartOfWorkingWeek(this DateTime value)
{
// Go back number to nearest Monday.
return value.AddDays(-((((int)value.DayOfWeek) + 6) % 7));
}
//-------------------------------------------------------------------
// Relative dates - month.
// Ref. http://stackoverflow.com/questions/24245523/getting-the-first-and-last-day-of-a-month-using-a-given-datetime-object for performance analysis.
public static DateTime StartOfMonth(this DateTime value)
{
return new DateTime(value.Year, value.Month, 1, 0, 0, 0, value.Kind);
}
public static int DaysInMonth(this DateTime value)
{
return DateTime.DaysInMonth(value.Year, value.Month);
}
public static DateTime EndOfMonth(this DateTime value)
{
return new DateTime(value.Year, value.Month, value.DaysInMonth(), 0, 0, 0, value.Kind);
}
//-------------------------------------------------------------------
// Relative dates - quarter.
public static DateTime StartOfQuarter(this DateTime value)
{
return new DateTime(value.Year, (((value.Month - 1) / 3) * 3) + 1, 1, 0, 0, 0, value.Kind);
}
public static int DaysInQuarter(this DateTime value)
{
DateTime startOfQuarter = value.StartOfQuarter();
return DateTime.DaysInMonth(startOfQuarter.Year, startOfQuarter.Month) + DateTime.DaysInMonth(startOfQuarter.Year, startOfQuarter.Month + 1) + DateTime.DaysInMonth(startOfQuarter.Year, startOfQuarter.Month + 2);
}
public static DateTime EndOfQuarter(this DateTime value)
{
DateTime startOfQuarter = value.StartOfQuarter();
return startOfQuarter.EndOfMonth().IncrementDate(1).EndOfMonth().IncrementDate(1).EndOfMonth();
}
//-------------------------------------------------------------------
// Relative dates - year.
public static DateTime StartOfYear(this DateTime value)
{
return new DateTime(value.Year, 1, 1, 0, 0, 0, value.Kind);
}
public static int DaysInYear(this DateTime value)
{
// To take leap year into account.
return 365 - 28 + DateTime.DaysInMonth(value.Year, 2);
}
public static DateTime EndOfYear(this DateTime value)
{
return new DateTime(value.Year, 12, 31, 0, 0, 0, value.Kind);
}
//-------------------------------------------------------------------
// Age calculation.
//-------------------------------------------------------------------
// Age calculation (returns 0 as a result of any exceptions):
public static ushort CalculateAge(DateTime dob, DateTime ageOn)
{
int ageInYears = 0;
if (IsValid(dob) && IsValid(ageOn) && dob < ageOn)
{
ageInYears = ageOn.Year - dob.Year;
if (ageOn.Month < dob.Month)
ageInYears--;
if (ageOn.Month == dob.Month && ageOn.Day < dob.Day)
ageInYears--;
}
return (ushort)ageInYears;
}
public static ushort CalculateAgeMonths(DateTime dob, DateTime ageOn)
{
int ageInMonths = 0;
if (IsValid(dob) && IsValid(ageOn) && dob < ageOn)
{
ageInMonths = (ageOn.Year - dob.Year) * 12;
// Will decrement the value if birth month > age month.
ageInMonths += ageOn.Month - dob.Month;
if (ageOn.Day < dob.Day)
ageInMonths--;
}
return (ushort)ageInMonths;
}
//-------------------------------------------------------------------
// C o n v e r s i o n
//-------------------------------------------------------------------
// Convert date to 32bit integer value.
/// <summary>
/// Convert Date (yyyyMMdd) to Int32 (C++ long).
/// <para>Used by legacy database columns to store date as 4 byte integer.</para>
/// </summary>
public static Int32 DateToInt32(this DateTime value)
{
if (value.Equals(DateTime.MinValue)) return 0;
Int32 dateValue = (value.Year << 16) + (value.Month << 8) + value.Day;
return dateValue;
}
/// <summary>
/// Convert Int32 (C++ long) to Date (yyyyMMdd).
/// <para>Used by legacy database columns to store date as 4 byte integer.</para>
/// </summary>
public static DateTime Int32ToDate(Int32 dateValue)
{
if (dateValue == 0) return DateTime.MinValue;
int year = ((dateValue >> 16) & 0xffff);
int month = ((dateValue >> 8) & 0xff);
int day = (dateValue & 0xff);
return new DateTime(year, month, day, 0, 0, 0, DateTimeKind.Utc);
}
//-------------------------------------------------------------------
/// <summary>
/// Convert Time (HHmmssnnn) to Int32 (C++ long).
/// <para>Used by legacy database columns to store time as 4 byte integer.</para>
/// </summary>
public static Int32 TimeToInt32(this DateTime value)
{
if (value.Equals(DateTime.MinValue)) return 0;
Int32 timeValue = (value.Hour << 24) + (value.Minute << 16) + (value.Second << 8) + (value.Millisecond / 10);
return timeValue;
}
/// <summary>
/// Convert Int32 (C++ long) to Time (HHmmssnnn).
/// <para>Used by legacy database columns to store time as 4 byte integer.</para>
/// </summary>
public static DateTime Int32ToTime(Int32 timeValue)
{
if (timeValue == 0) return DateTime.MinValue;
int year = DateTime.Today.Year;
int month = DateTime.Today.Month;
int day = DateTime.Today.Day;
int hour = (timeValue >> 24) & 0xff;
int minute = (timeValue >> 16) & 0xff;
int second = (timeValue >> 8) & 0xff;
int millis = timeValue & 0xff;
return new DateTime(year, month, day, hour, minute, second, DateTimeKind.Utc).AddMilliseconds(millis);
}
//-------------------------------------------------------------------
// F o r m a t t i n g
//-------------------------------------------------------------------
/// <summary>
/// <para>Format Date/time using format string.</para>
/// <para><b>d, dd</b>: Day of month</para>
/// <para><b>ddd, dddd</b>: Day of week</para>
/// <para><b>M, MM</b>: Month (1 to 12)</para>
/// <para><b>MMM, MMMM</b>: Month name</para>
/// <para><b>y - yyyy</b>: Year</para>
/// <para><b>H, HH</b>: hour (24 hour clock)</para>
/// <para><b>h, hh</b>: hour (12 hour clock)</para>
/// <para><b>t, tt</b>: AM/PM designator</para>
/// <para><b>m, mm</b>: minute</para>
/// <para><b>s, ss</b>: second</para>
/// <para><b>f</b>: 10ths second</para>
/// <para><b>fff</b>: 1,000ths second</para>
/// <para><b>ffffff</b>: 1,000,000ths second</para>
/// <para><b>F</b>: 10ths second (non zero) etc</para>
/// <para><b>g, gg</b>: Period or era</para>
/// </summary>
/// <param name="value"></param>
/// <param name="format"></param>
/// <returns></returns>
public static String Format(this DateTime value, String format, CultureInfo cultureInfo = null)
{
return Format((DateTime?)value, format, cultureInfo);
}
public static String Format(this DateTime? value, String format, CultureInfo cultureInfo = null)
{
if (!IsValid(value)) return String.Empty;
try
{
if (!String.IsNullOrEmpty(format))
{
return ((DateTime)value).ToString(format, cultureInfo ?? CultureInfo.InvariantCulture);
}
return ((DateTime)value).ToString(cultureInfo ?? CultureInfo.InvariantCulture);
}
catch (Exception exc)
{
SaLogger.Debug($"DateUtil.Format {ExceptionUtil.GetErrorStateMessage(exc)}");
}
return String.Empty;
}
//-------------------------------------------------------------------
/// <summary>Format as SQL date: 'yyyy-MM-dd'</summary>
public static String FormatSqlDate(this DateTime value)
{
return Format(value, Format_SqlDate);
}
/// <summary>Format as SQL time: 'HH:mm:ss'</summary>
public static String FormatSqlTime(this DateTime value)
{
return Format(value, Format_SqlTime);
}
/// <summary>Format as SQL datetime: 'yyyy-MM-ddTHH:mm:ss'</summary>
public static String FormatSqlDateTime(this DateTime value)
{
return Format(value, Format_SqlDateTime);
}
//-------------------------------------------------------------------
/// <summary>Format as XML date: 'yyyy-MM-dd'</summary>
public static String FormatXmlDate(this DateTime value)
{
return Format(value, Format_XmlDate);
}
/// <summary>Format as XML time: 'HH:mm:ss'</summary>
public static String FormatXmlTime(this DateTime value)
{
return Format(value, Format_XmlTime);
}
/// <summary>Format as XML datetime: 'yyyy-MM-ddTHH:mm:ss'</summary>
public static String FormatXmlDateTime(this DateTime value)
{
return Format(value, Format_XmlDateTime);
}
//-------------------------------------------------------------------
/// <summary>Format suitable for parsing as javascript date: 'yyyy-MM-dd'</summary>
public static String FormatDateForJavascript(this DateTime? value)
{
return Format(value, Format_JavascriptDate);
}
//-------------------------------------------------------------------
/// <summary>Format number of seconds.</summary>
public static String FormatNumberOfSeconds(int numberOfSeconds)
{
int numberOfMinutes = numberOfSeconds / 60;
int numberOfHours = numberOfMinutes / 60;
int numberOfDays = numberOfHours / 24;
String elapsed = "";
if (numberOfDays > 0)
elapsed += numberOfDays + "d ";
if (numberOfHours > 0)
elapsed += (numberOfHours % 24) + "h ";
if (numberOfMinutes > 0)
elapsed += (numberOfMinutes % 60) + "m ";
if (StringUtil.IsEmpty(elapsed) || numberOfSeconds % 60 > 0)
elapsed += (numberOfSeconds % 60) + "s ";
return elapsed.Trim();
}
/// <summary>Format number of seconds, including millis where value <= 10s.</summary>
public static String FormatNumberOfSeconds(double numberOfSeconds)
{
int numberOfMinutes = (int)(numberOfSeconds / 60);
int numberOfHours = numberOfMinutes / 60;
int numberOfDays = numberOfHours / 24;
String elapsed = "";
if (numberOfDays > 0)
elapsed += numberOfDays + "d ";
if (numberOfHours > 0)
elapsed += (numberOfHours % 24) + "h ";
if (numberOfMinutes > 0)
elapsed += (numberOfMinutes % 60) + "m ";
if (StringUtil.IsEmpty(elapsed) || numberOfSeconds % 60 > 0)
{
int millis = (int)((numberOfSeconds - (int)numberOfSeconds) * 1000);
if (numberOfSeconds > 10.0)
elapsed += (int)(numberOfSeconds % 60) + "s ";
else elapsed += (int)(numberOfSeconds % 60) + "." + millis.ToString("D3") + "s ";
}
return elapsed.Trim();
}
//-------------------------------------------------------------------
// L i s t S o r t i n g
//-------------------------------------------------------------------
public static void SortAscending(this List<DateTime> list)
{
list.Sort((a, b) => a.CompareTo(b));
}
public static void SortDescending(this List<DateTime> list)
{
list.Sort((a, b) => b.CompareTo(a));
}
//-------------------------------------------------------------------
}
//=======================================================================
}