using System;
using System.Globalization;
using System.Linq;
using CH = Developpez.Dotnet.CompatibilityHelper;
namespace Developpez.Dotnet
{
///
/// Permet d'utiliser des methodes d'extensions sur les types de Date.
/// En particulier, permet d'utiliser des expressions "à la Ruby" en C#
/// Par exemple, plutôt que de faire DateTime.Now.AddMinutes(5), on fera:
/// 5.Minutes().FromNow()
/// )
public static partial class DateExtensions
{
#region Current time abstraction (for testability)
private static readonly ITimeProvider _defaultTimeProvider = new DefaultTimeProvider();
private static ITimeProvider _timeProvider = _defaultTimeProvider;
internal static ITimeProvider TimeProvider
{
get
{
return _timeProvider;
}
set
{
value.CheckArgumentNull("value");
_timeProvider = value;
}
}
internal static void ResetTimeProvider()
{
_timeProvider = _defaultTimeProvider;
}
internal interface ITimeProvider
{
DateTime Now { get; }
DateTime UtcNow { get; }
}
private class DefaultTimeProvider : ITimeProvider
{
public DateTime Now
{
get { return DateTime.Now; }
}
public DateTime UtcNow
{
get { return DateTime.UtcNow; }
}
}
#endregion
#region Durations
///
/// Retourne une durée correspondant au nombre de jours passé en paramètre
///
/// Nombre de jours dans la période
/// Une durée correspondant au nombre de jours spécifié
public static TimeSpan Days(this int value)
{
return TimeSpan.FromDays(value);
}
///
/// Retourne une durée correspondant au nombre d'heures passé en paramètre
///
/// Nombre de heures dans la période
/// Une durée correspondant au nombre d'heures spécifié
public static TimeSpan Hours(this int value)
{
return TimeSpan.FromHours(value);
}
///
/// Retourne une durée correspondant au nombre de minutes passé en paramètre
///
/// Nombre de minutes dans la période
/// Une durée correspondant au nombre de minutes spécifié
public static TimeSpan Minutes(this int value)
{
return TimeSpan.FromMinutes(value);
}
///
/// Retourne une durée correspondant au nombre de secondes passé en paramètre
///
/// Nombre de secondes dans la période
/// Une durée correspondant au nombre de secondes spécifié
public static TimeSpan Seconds(this int value)
{
return TimeSpan.FromSeconds(value);
}
///
/// Retourne une durée correspondant au nombre de millisecondes passé en paramètre
///
/// Nombre de millisecondes dans la période
/// Une durée correspondant au nombre de millisecondes spécifié
public static TimeSpan Milliseconds(this int value)
{
return TimeSpan.FromMilliseconds(value);
}
#endregion
#region Relative dates
///
/// Retourne une date correspondant à la durée précédant la date courante
///
/// Durée que l'on veut soustraire a la date courante
/// La date correspondant à la durée précédent la date courante
public static DateTime Ago(this TimeSpan timeSpan)
{
return TimeProvider.Now - timeSpan;
}
///
/// Retourne une date correspondant à la durée suivant la date courante
///
/// Durée que l'on veut ajouter a la date courante
/// La date correspondant à la durée suivant la date courante
public static DateTime FromNow(this TimeSpan timeSpan)
{
return TimeProvider.Now + timeSpan;
}
///
/// Retourne une date correspondant à la durée suivant la date de départ spécifiée
///
/// Durée que l'on veut ajouter a la date de départ spécifiée
/// Date de départ
/// La date correspondant à la durée suivant la date de départ spécifiée
public static DateTime From(this TimeSpan timeSpan, DateTime startDate)
{
return startDate + timeSpan;
}
///
/// Retourne une date correspondant à la durée précédent la date spécifiée
///
/// Durée que l'on veut retrancher de la date spécifiée
/// Date de fin
/// La date correspondant à la durée précédent la date spécifiée
public static DateTime Before(this TimeSpan timeSpan, DateTime endDate)
{
return endDate - timeSpan;
}
#endregion
#region Time of day
///
/// Renvoie une date correspondant à l'heure spécifiée pour la date spécifiée.
///
/// Date
/// Heure
/// La date correspondant à l'heure spécifiée pour la date spécifiée.
public static DateTime At(this DateTime date, TimeSpan time)
{
return date.Date.Add(time);
}
///
/// Renvoie une date correspondant à l'heure spécifiée pour la date spécifiée.
///
/// Date
/// Composantes de l'heure
/// La date correspondant à l'heure spécifiée pour la date spécifiée.
public static DateTime At(this DateTime date, params TimeSpan[] timeParts)
{
return timeParts.Aggregate(date.Date, (d, t) => d.Add(t));
}
///
/// Renvoie une date correspondant à l'heure spécifiée pour la date spécifiée.
///
/// Date
/// Heures
/// La date correspondant à l'heure spécifiée pour la date spécifiée.
public static DateTime At(this DateTime date, int hours)
{
return date.Date.AddHours(hours);
}
///
/// Renvoie une date correspondant à l'heure spécifiée pour la date spécifiée.
///
/// Date
/// Heures
/// Minutes
/// La date correspondant à l'heure spécifiée pour la date spécifiée.
public static DateTime At(this DateTime date, int hours, int minutes)
{
return date.Date.AddHours(hours).AddMinutes(minutes);
}
///
/// Renvoie une date correspondant à l'heure spécifiée pour la date spécifiée.
///
/// Date
/// Heures
/// Minutes
/// Secondes
/// La date correspondant à l'heure spécifiée pour la date spécifiée.
public static DateTime At(this DateTime date, int hours, int minutes, int seconds)
{
return date.Date.AddHours(hours).AddMinutes(minutes).AddSeconds(seconds);
}
#endregion
#region UNIX timestamps
private static readonly DateTime _unixEpoch = new DateTime(1970, 1, 1);
///
/// Convertit un DateTime en timestamp Unix
///
/// La date à convertir
/// Le timestamp Unix équivalent
public static long ToUnixTimestamp(this DateTime date)
{
TimeSpan span = date - _unixEpoch;
return (long)CH.Math.Truncate(span.TotalSeconds);
}
///
/// Convertit un timestamp Unix en DateTime
///
/// Le timestamp à convertir
/// Le DateTime équivalent
public static DateTime ToDateTime(this long unixTimestamp)
{
return _unixEpoch.AddSeconds(unixTimestamp);
}
#endregion
#region Next/previous day of week
///
/// Renvoie le prochain jour de la semaine spécifié à partir d'une date
///
/// la date à partir de laquelle on recherche
/// le jour de la semaine recherché
/// Le prochain dayOfWeek spécifié à partir de la date
public static DateTime Next(this DateTime from, DayOfWeek dayOfWeek)
{
int start = (int)from.DayOfWeek;
int wanted = (int)dayOfWeek;
if (wanted <= start)
wanted += 7;
return from.AddDays(wanted - start);
}
///
/// Renvoie le prochain jour de la semaine spécifié à partir de la date courante
///
/// le jour de la semaine recherché
/// Le prochain dayOfWeek spécifié à partir de la date courante
public static DateTime Next(this DayOfWeek dayOfWeek)
{
return TimeProvider.Now.Next(dayOfWeek);
}
///
/// Renvoie le précédent jour de la semaine spécifié à partir d'une date
///
/// la date à partir de laquelle on recherche
/// le jour de la semaine recherché
/// Le précédent dayOfWeek spécifié à partir de la date
public static DateTime Previous(this DateTime from, DayOfWeek dayOfWeek)
{
int end = (int)from.DayOfWeek;
int wanted = (int)dayOfWeek;
if (wanted >= end)
end += 7;
return from.AddDays(wanted - end);
}
///
/// Renvoie le précédent jour de la semaine spécifié à partir de la date courante
///
/// le jour de la semaine recherché
/// Le précédent dayOfWeek spécifié à partir de la date courante
public static DateTime Previous(DayOfWeek dayOfWeek)
{
return TimeProvider.Now.Previous(dayOfWeek);
}
#endregion
#region Working days
///
/// Ajoute le nombre spécifié de jours ouvrés à la date
///
/// Date d'origine
/// Nombre de jours ouvrés à ajouter
/// La date days jours ouvrés après date (ou avant si days est négatif)
/// Cette méthode suppose que la semaine comporte 5 jours ouvrés, du lundi au vendredi. Les jours fériés ne sont pas pris en compte.
public static DateTime AddWorkingDays(this DateTime date, int days)
{
if (days == 0)
return date;
int sign = days < 0 ? -1 : 1;
while (days % 5 != 0 || !date.IsWorkingDay())
{
date = date.AddDays(sign);
if (!date.IsWorkingDay())
continue;
days -= sign;
}
int nWeekEnds = days / 5;
DateTime result = date.AddDays(days + nWeekEnds * 2);
return result;
}
///
/// Indique si la date est un jour ouvré
///
/// La date à vérifier
/// true si la date est un jour ouvré, sinon false.
/// Cette méthode suppose que la semaine comporte 5 jours ouvrés, du lundi au vendredi. Les jours fériés ne sont pas pris en compte.
public static bool IsWorkingDay(this DateTime date)
{
return !(date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday);
}
#endregion
#region Start of week
///
/// Calcule la date du début de la semaine spécifiée, en utilisant la règle
/// de calcul et le premier jour de la semaine de la culture courante.
///
///Année
///Numéro de semaine
///La date de début de la semaine spécifiée
///L'un des paramètres
/// est en dehors de sa plage de valeurs autorisée. Peut se produire
/// notamment si l'année year a moins de weekNumber
/// semaines.
public static DateTime GetStartOfWeek(int year, int weekOfYear)
{
return GetStartOfWeek(
year,
weekOfYear,
CultureInfo.CurrentCulture.DateTimeFormat.CalendarWeekRule,
CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek);
}
///
/// Calcule la date du début de la semaine spécifiée, en utilisant la règle
/// de calcul spécifiée et le premier jour de la semaine de la culture courante.
///
///Année
///Numéro de semaine
///Règle de calcul du numéro de semaine
///La date de début de la semaine spécifiée
///L'un des paramètres
/// est en dehors de sa plage de valeurs autorisée. Peut se produire
/// notamment si l'année year a moins de weekNumber
/// semaines.
public static DateTime GetStartOfWeek(int year, int weekOfYear, CalendarWeekRule rule)
{
return GetStartOfWeek(
year,
weekOfYear,
rule,
CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek);
}
///
/// Calcule la date du début de la semaine spécifiée, en utilisant la règle
/// de calcul de la culture courante et le premier jour de la semaine spécifié.
///
///Année
///Numéro de semaine
///Premier jour de la semaine
///La date de début de la semaine spécifiée
///L'un des paramètres
/// est en dehors de sa plage de valeurs autorisée. Peut se produire
/// notamment si l'année year a moins de weekNumber
/// semaines.
public static DateTime GetStartOfWeek(int year, int weekOfYear, DayOfWeek firstDayOfWeek)
{
return GetStartOfWeek(
year,
weekOfYear,
CultureInfo.CurrentCulture.DateTimeFormat.CalendarWeekRule,
firstDayOfWeek);
}
///
/// Calcule la date du début de la semaine spécifiée, en utilisant la règle
/// de calcul spécifiée et le premier jour de la semaine spécifié.
///
///Année
///Numéro de semaine
///Règle de calcul du numéro de semaine
///Premier jour de la semaine
///La date de début de la semaine spécifiée
///L'un des paramètres
/// est en dehors de sa plage de valeurs autorisée. Peut se produire
/// notamment si l'année year a moins de weekNumber
/// semaines.
public static DateTime GetStartOfWeek(int year, int weekOfYear, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
{
year.CheckArgumentOutOfRange("year", 1, 9999);
weekOfYear.CheckArgumentOutOfRange("weekOfYear", 1, 54);
firstDayOfWeek.CheckArgumentInEnum("firstDayOfWeek");
switch (rule)
{
case CalendarWeekRule.FirstDay:
return GetStartOfWeekWithFirstDayRule(year, weekOfYear, firstDayOfWeek);
case CalendarWeekRule.FirstFullWeek:
return GetStartOfWeekWithFirstFullWeekRule(year, weekOfYear, firstDayOfWeek);
case CalendarWeekRule.FirstFourDayWeek:
return GetStartOfWeekWithFirstFourDayWeekRule(year, weekOfYear, firstDayOfWeek);
default:
throw new ArgumentOutOfRangeException("rule");
}
}
static DateTime GetStartOfWeekWithFirstDayRule(int year, int weekOfYear, DayOfWeek firstDayOfWeek)
{
DateTime jan1 = new DateTime(year, 1, 1);
DateTime startOfFirstWeek =
jan1.DayOfWeek == firstDayOfWeek
? jan1
: jan1.Previous(firstDayOfWeek);
DateTime startOfWeek = startOfFirstWeek.AddDays(7 * (weekOfYear - 1));
if (startOfWeek.Year < year)
return jan1;
if (startOfWeek.Year > year)
throw new ArgumentOutOfRangeException("weekOfYear");
return startOfWeek;
}
static DateTime GetStartOfWeekWithFirstFullWeekRule(int year, int weekOfYear, DayOfWeek firstDayOfWeek)
{
DateTime jan1 = new DateTime(year, 1, 1);
int diff = ((firstDayOfWeek - jan1.DayOfWeek) + 7) % 7;
DateTime startOfFirstWeek = jan1.AddDays(diff);
DateTime startOfWeek = startOfFirstWeek.AddDays(7 * (weekOfYear - 1));
if (startOfWeek.Year > year)
throw new ArgumentOutOfRangeException("weekOfYear");
return startOfWeek;
}
static DateTime GetStartOfWeekWithFirstFourDayWeekRule(int year, int weekOfYear, DayOfWeek firstDayOfWeek)
{
DateTime jan1 = new DateTime(year, 1, 1);
DayOfWeek fourthDayOfWeek = (DayOfWeek)(((int)firstDayOfWeek + 3) % 7);
DateTime firstFourDayOfWeek =
jan1.DayOfWeek == fourthDayOfWeek
? jan1
: jan1.Next(fourthDayOfWeek);
DateTime startOfFirstWeek = firstFourDayOfWeek.Previous(firstDayOfWeek);
DateTime startOfWeek = startOfFirstWeek.AddDays(7 * (weekOfYear - 1));
if (startOfWeek.Year < year)
return jan1;
if (startOfWeek.Year > year)
throw new ArgumentOutOfRangeException("weekOfYear");
return startOfWeek;
}
#endregion
#region WeekOfYear
///
/// Calcule le numéro de la semaine à laquelle appartient une date, en utilisant la règle de calcul
/// et le premier jour de la semaine de la culture courante.
///
/// La date dont on veut le numéro de semaine
/// Le numéro de la semaine à laquelle appartient la date
public static int WeekOfYear(this DateTime date)
{
return WeekOfYear(date, CalendarWeekRule.FirstDay);
}
///
/// Calcule le numéro de la semaine à laquelle appartient une date, en utilisant la règle de calcul
/// spécifiée et le premier jour de la semaine de la culture courante.
///
/// La date dont on veut le numéro de semaine
/// La règle de calcul du numéro de semaine à utiliser
/// Le numéro de la semaine à laquelle appartient la date
public static int WeekOfYear(this DateTime date, CalendarWeekRule rule)
{
return date.WeekOfYear(rule, CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek);
}
///
/// Calcule le numéro de la semaine à laquelle appartient une date, en utilisant la règle de calcul
/// de la culture courante, et le premier jour de la semaine spécifié.
///
/// La date dont on veut le numéro de semaine
/// Le premier jour de la semaine
/// Le numéro de la semaine à laquelle appartient la date
public static int WeekOfYear(this DateTime date, DayOfWeek firstDayOfWeek)
{
return date.WeekOfYear(CultureInfo.CurrentCulture.DateTimeFormat.CalendarWeekRule, firstDayOfWeek);
}
///
/// Calcule le numéro de la semaine à laquelle appartient une date, en utilisant la règle de calcul
/// et le premier jour de la semaine spécifiés.
///
/// La date dont on veut le numéro de semaine
/// La règle de calcul du numéro de semaine à utiliser
/// Le premier jour de la semaine
/// Le numéro de la semaine à laquelle appartient la date
public static int WeekOfYear(this DateTime date, CalendarWeekRule rule, DayOfWeek firstDayOfWeek)
{
return CultureInfo.CurrentCulture.Calendar.GetWeekOfYear(date, rule, firstDayOfWeek);
}
#endregion
#region Misc
///
/// Tronque une date au composant (année, mois, jour, heure, minute, seconde, milliseconde) spécifié
///
/// Date à tronquer
/// Composant auquel la date doit être tronquée
/// La date tronquée au composant spécifié
public static DateTime Truncate(this DateTime date, DateTimeComponent component)
{
int year = 1;
int month = 1;
int day = 1;
int hour = 0;
int minute = 0;
int second = 0;
int millisecond = 0;
if (component <= DateTimeComponent.Millisecond)
millisecond = date.Millisecond;
if (component <= DateTimeComponent.Second)
second = date.Second;
if (component <= DateTimeComponent.Minute)
minute = date.Minute;
if (component <= DateTimeComponent.Hour)
hour = date.Hour;
if (component <= DateTimeComponent.Day)
day = date.Day;
if (component <= DateTimeComponent.Month)
month = date.Month;
if (component <= DateTimeComponent.Year)
year = date.Year;
return new DateTime(year, month, day, hour, minute, second, millisecond, date.Kind);
}
#endregion
}
///
/// Représente les différents composants d'une date
///
public enum DateTimeComponent
{
///
/// Milliseconde
///
Millisecond,
///
/// Seconde
///
Second,
///
/// Minute
///
Minute,
///
/// Heure
///
Hour,
///
/// Jour
///
Day,
///
/// Mois
///
Month,
///
/// Année
///
Year
}
}