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 } }