1475 lines
44 KiB
C#
1475 lines
44 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.DataAnnotations;
|
|
using System.Linq;
|
|
using EnVisage.Code;
|
|
using System.Globalization;
|
|
using System.Web.Script.Serialization;
|
|
|
|
namespace EnVisage.Models
|
|
{
|
|
#region Holiday item models
|
|
|
|
public class HolidayModel : IBaseModel<Holiday>
|
|
{
|
|
#region Enums
|
|
|
|
/// <summary>
|
|
/// Status of the Calendar Settings record
|
|
/// </summary>
|
|
public enum HolidayStatus
|
|
{
|
|
/// <summary>
|
|
/// NoValue = 0.
|
|
/// </summary>
|
|
[DisplayValue("No Value")]
|
|
NoValue = 0,
|
|
/// <summary>
|
|
/// Inactive = 0.
|
|
/// </summary>
|
|
[DisplayValue("Inactive")]
|
|
Inactive = 1,
|
|
/// <summary>
|
|
/// Active = 1.
|
|
/// </summary>
|
|
[DisplayValue("Active")]
|
|
Active = 2,
|
|
/// <summary>
|
|
/// Inactive = 2.
|
|
/// </summary>
|
|
[DisplayValue("Future")]
|
|
Future = 3,
|
|
}
|
|
|
|
public enum HolidayOccurrence
|
|
{
|
|
[DisplayValue("Each")]
|
|
SameDayEveryYear = 0,
|
|
[DisplayValue("First")]
|
|
FirstDayOfWeek = 1,
|
|
[DisplayValue("Second")]
|
|
SecondDayOfWeek = 2,
|
|
[DisplayValue("Third")]
|
|
ThirdDayOfWeek = 3,
|
|
[DisplayValue("Fourth")]
|
|
FourthDayOfWeek = 4,
|
|
[DisplayValue("Last")]
|
|
LastDayOfWeek = 5
|
|
}
|
|
#endregion
|
|
#region Constructors
|
|
|
|
public HolidayModel()
|
|
{
|
|
Id = Guid.Empty;
|
|
HolidayGroupId = Guid.Empty;
|
|
CompanyImpactAllResources = true;
|
|
Status = HolidayStatus.NoValue;
|
|
CreatedAt = DateTime.UtcNow;
|
|
Teams = new List<Guid>();
|
|
Resources = new List<Guid>();
|
|
ExpenditureCategories = new List<Guid>();
|
|
}
|
|
|
|
#endregion
|
|
#region Properties
|
|
|
|
public Guid Id { get; set; }
|
|
public Guid HolidayGroupId { get; set; }
|
|
|
|
public string Options { get; set; }
|
|
/// <summary>
|
|
/// Gets or sets a name of the holiday.
|
|
/// </summary>
|
|
[Required]
|
|
[DataType(DataType.Text)]
|
|
[StringLength(50)]
|
|
[Display(Name = "Name")]
|
|
public string Name { get; set; }
|
|
|
|
[Display(Name = "Start Date")]
|
|
[DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:M/d/yyyy}", ApplyFormatInEditMode = true)]
|
|
[Required]
|
|
public DateTime? StartDate { get; set; }
|
|
|
|
[Display(Name = "End Date")]
|
|
[DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:M/d/yyyy}", ApplyFormatInEditMode = true)]
|
|
[RequiredIf("MultipleDaysHoliday", true, ErrorMessage = "The End Date field is required.")]
|
|
[DateGreaterThanOrEqual("StartDate", "End Date must be later or equal to Start Date", ValidateIfEmpty = false)]
|
|
public DateTime? EndDate { get; set; }
|
|
|
|
public bool MultipleDaysHoliday { get; set; }
|
|
|
|
[Display(Name = "Business Unit Impact")]
|
|
public bool CompanyImpactAllResources { get; set; }
|
|
|
|
[Display(Name = "From Team(s)")]
|
|
public List<Guid> Teams { get; set; }
|
|
|
|
|
|
[Display(Name = "Resource Names")]
|
|
public List<Guid> Resources { get; set; }
|
|
|
|
|
|
[Display(Name = "Expenditure Categories")]
|
|
public List<Guid> ExpenditureCategories { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether a holiday is a working day or not.
|
|
/// </summary>
|
|
[Display(Name = "Working Days")]
|
|
public bool WorkingDays { get; set; }
|
|
|
|
[Required]
|
|
[DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)]
|
|
[Display(Name = "Effective Date of Change")]
|
|
public DateTime? EffectiveChangeDate { get; set; }
|
|
|
|
[DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)]
|
|
[Display(Name = "Valid To")]
|
|
public DateTime? ValidTo { get; set; }
|
|
|
|
public DateTime CreatedAt { get; set; }
|
|
|
|
[Display(Name = "Status")]
|
|
public HolidayStatus Status { get; set; }
|
|
|
|
public bool IsInclude { get; set; }
|
|
public bool IncludexpenditureCategories { get; set; }
|
|
|
|
protected HolidayBaseRecurrenceModel recurrences = null;
|
|
public HolidayBaseRecurrenceModel Recurrences
|
|
{
|
|
get
|
|
{
|
|
if ((recurrences == null) && !String.IsNullOrEmpty(this.Options))
|
|
{
|
|
JavaScriptSerializer serializer = new JavaScriptSerializer();
|
|
HolidayRecurrenceJsonModel jsonModel = (HolidayRecurrenceJsonModel)serializer.Deserialize(this.Options,
|
|
typeof(HolidayRecurrenceJsonModel));
|
|
|
|
recurrences = jsonModel.Convert();
|
|
|
|
// Assign number of days in the Holiday
|
|
recurrences.NumberOfDays =
|
|
this.MultipleDaysHoliday && this.StartDate.HasValue && this.EndDate.HasValue ?
|
|
Convert.ToInt32((this.EndDate.Value - this.StartDate.Value).TotalDays) + 1 : 1;
|
|
}
|
|
return recurrences;
|
|
}
|
|
}
|
|
|
|
public List<DateTime> GetHolidayDates()
|
|
{
|
|
List<DateTime> result = new List<DateTime>();
|
|
|
|
if (this.StartDate.HasValue && this.EffectiveChangeDate.HasValue)
|
|
{
|
|
int daysCount = this.MultipleDaysHoliday ?
|
|
Convert.ToInt32((this.EndDate.Value - this.StartDate.Value).TotalDays) + 1 : 1;
|
|
|
|
if (String.IsNullOrEmpty(this.Options))
|
|
{
|
|
// Non recurrentive holiday
|
|
for (int index = 0; index < daysCount; index++)
|
|
{
|
|
DateTime currentDate = this.StartDate.Value.AddDays(index);
|
|
if (currentDate >= this.EffectiveChangeDate.Value)
|
|
result.Add(currentDate);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Recurrentive holiday. Calculate dates through recurrence
|
|
result = this.Recurrences.GetDates(this.EffectiveChangeDate.Value, Constants.FISCAL_CALENDAR_MAX_DATE);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Determines whether the specified object is valid.
|
|
/// </summary>
|
|
/// <param name="validationContext">The validation context.</param>
|
|
/// <returns>A collection that holds failed-validation information.</returns>
|
|
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
|
{
|
|
if (StartDate.HasValue && EndDate.HasValue && MultipleDaysHoliday && (StartDate > EndDate.Value))
|
|
{
|
|
yield return new ValidationResult("End Date must be later or equal to Start Date", new[] { "EndDate" });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Casts a <see cref="Holiday"/> obect to the object of type <see cref="HolidayModel"/>.
|
|
/// </summary>
|
|
/// <param name="obj">A <see cref="Holiday"/> object.</param>
|
|
/// <returns>A <see cref="HolidayModel"/> object filled with data from db.</returns>
|
|
public static explicit operator HolidayModel(Holiday obj)
|
|
{
|
|
if (obj == null)
|
|
return null;
|
|
|
|
var model = new HolidayModel
|
|
{
|
|
Id = obj.Id,
|
|
HolidayGroupId = obj.HolidayGroupId,
|
|
Name = obj.Name,
|
|
WorkingDays = obj.WorkingDays,
|
|
EffectiveChangeDate = obj.EffectiveChangeDate,
|
|
CreatedAt = obj.CreatedAt,
|
|
Options = obj.Options ?? string.Empty,
|
|
StartDate = obj.StartDate,
|
|
EndDate = obj.EndDate,
|
|
MultipleDaysHoliday = obj.EndDate.HasValue && (obj.StartDate != obj.EndDate.Value),
|
|
CompanyImpactAllResources = obj.CompanyImpact,
|
|
Teams = obj.Holiday2Team.Select(x => x.TeamId).ToList(),
|
|
Resources = obj.Holiday2PeopleResource.Select(x => x.ResourceId).ToList(),
|
|
ExpenditureCategories = obj.Holiday2ExpenditureCategory.Select(x => x.ExpenditureCategoryId).ToList(),
|
|
IsInclude = obj.IsInclude
|
|
};
|
|
|
|
model.TrimStringProperties();
|
|
return model;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copies data from model to DAL object.
|
|
/// </summary>
|
|
/// <param name="holiday">A target DAL object.</param>
|
|
public void CopyTo(Holiday holiday)
|
|
{
|
|
if (holiday == null)
|
|
throw new ArgumentNullException("holiday");
|
|
|
|
if (!EffectiveChangeDate.HasValue)
|
|
throw new Exception("Effective Date of Change must be set");
|
|
|
|
holiday.Id = Id;
|
|
holiday.HolidayGroupId = HolidayGroupId;
|
|
holiday.Name = Name;
|
|
holiday.WorkingDays = WorkingDays;
|
|
holiday.EffectiveChangeDate = EffectiveChangeDate.Value;
|
|
holiday.CreatedAt = CreatedAt;
|
|
holiday.Options = string.IsNullOrEmpty(Options) ? null : Options;
|
|
holiday.StartDate = StartDate.Value;
|
|
holiday.EndDate = EndDate;
|
|
holiday.CompanyImpact = CompanyImpactAllResources;
|
|
holiday.IsInclude = IsInclude;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
/// <summary>
|
|
/// Model for displaying Holidays in Grids
|
|
/// </summary>
|
|
public class HolidayDisplayModel
|
|
{
|
|
#region Properties
|
|
|
|
public Guid Id { get; set; }
|
|
public Guid HolidayGroupId { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a name of the holiday.
|
|
/// </summary>
|
|
[DataType(DataType.Text)]
|
|
[Display(Name = "Name")]
|
|
public string Name { get; set; }
|
|
|
|
public DateTime? StartDate { get; set; }
|
|
public DateTime? EndDate { get; set; }
|
|
|
|
[Display(Name = "Start Date")]
|
|
public string StartDateText { get; set; }
|
|
|
|
[Display(Name = "End Date")]
|
|
public string EndDateText { get; set; }
|
|
|
|
/// <summary>
|
|
/// Gets or sets a value indicating whether a holiday is a working day or not.
|
|
/// </summary>
|
|
[Display(Name = "Working Days")]
|
|
public bool WorkingDays { get; set; }
|
|
|
|
/// <summary>
|
|
/// Text description of the holiday recurrency rules
|
|
/// </summary>
|
|
[Display(Name = "Recurrences")]
|
|
public string Recurrences { get; set; }
|
|
|
|
public DateTime EffectiveChangeDate { get; set; }
|
|
|
|
[Display(Name = "Effective Date of Change")]
|
|
public string EffectiveChangeDateAsText { get; set; }
|
|
|
|
public DateTime? ValidTo { get; set; }
|
|
|
|
[Display(Name = "Valid To")]
|
|
public string ValidToAsText { get; set; }
|
|
|
|
public HolidayModel.HolidayStatus Status { get; set; }
|
|
|
|
[Display(Name = "Status")]
|
|
public string StatusText { get; set; }
|
|
|
|
public bool IsMultidaysHoliday
|
|
{
|
|
get
|
|
{
|
|
return StartDate != EndDate;
|
|
}
|
|
}
|
|
|
|
public bool IsInclude { get; set; }
|
|
|
|
#endregion
|
|
|
|
public static explicit operator HolidayDisplayModel(HolidayModel obj)
|
|
{
|
|
if (obj == null)
|
|
return null;
|
|
|
|
var model = new HolidayDisplayModel
|
|
{
|
|
Id = obj.Id,
|
|
HolidayGroupId = obj.HolidayGroupId,
|
|
Name = obj.Name,
|
|
StartDate = obj.StartDate,
|
|
StartDateText = obj.StartDate.HasValue ? obj.StartDate.Value.ToShortDateString() : String.Empty,
|
|
EndDate = obj.EndDate,
|
|
EndDateText = obj.EndDate.HasValue ? obj.EndDate.Value.ToShortDateString() : String.Empty,
|
|
WorkingDays = obj.WorkingDays,
|
|
EffectiveChangeDate = obj.EffectiveChangeDate.Value,
|
|
EffectiveChangeDateAsText = obj.EffectiveChangeDate.Value.ToShortDateString(),
|
|
ValidTo = obj.ValidTo,
|
|
ValidToAsText = obj.ValidTo.HasValue ? obj.ValidTo.Value.ToShortDateString() : String.Empty,
|
|
Status = obj.Status,
|
|
StatusText = obj.Status.ToDisplayValue(),
|
|
Recurrences = obj.Options,
|
|
IsInclude = obj.IsInclude
|
|
};
|
|
return model;
|
|
}
|
|
}
|
|
|
|
public class HolidayListModel
|
|
{
|
|
public HolidayListModel()
|
|
{
|
|
Items = new List<HolidayDisplayModel>();
|
|
}
|
|
|
|
public List<HolidayDisplayModel> Items { get; set; }
|
|
}
|
|
|
|
#endregion
|
|
#region Holiday recurrence models
|
|
|
|
/// <summary>
|
|
/// Internal model for serialization / deserialization of recurrences JSON text
|
|
/// </summary>
|
|
/// <remarks>Use Convert methods to convert it to suitable for data manipulation model</remarks>
|
|
public class HolidayRecurrenceJsonModel
|
|
{
|
|
public int periodOption;
|
|
public int? dailyOption;
|
|
public int? dailyDay;
|
|
public int? weeklyWeeks;
|
|
public bool? Sunday;
|
|
public bool? Monday;
|
|
public bool? Tuesday;
|
|
public bool? Wednesday;
|
|
public bool? Thursday;
|
|
public bool? Friday;
|
|
public bool? Saturday;
|
|
public int? monthlyOption;
|
|
public int? monthlyDate;
|
|
public int? monthlyMonths;
|
|
public string monthlyOccurrence;
|
|
public string monthlyWeek;
|
|
public int? monthlyMonth;
|
|
public int? yearlyOption;
|
|
public int? yearlyYear;
|
|
public int? yearlyMonth;
|
|
public int? yearlyMonthNumber;
|
|
public string yearlyOccurrence;
|
|
public string yearlyWeek;
|
|
public int? yearlyMonth2;
|
|
public string range_start_picker;
|
|
public int? rangeOption;
|
|
public int? Occurrences;
|
|
public string range_picker;
|
|
public long? StartDate;
|
|
public long? EndDate;
|
|
|
|
|
|
public HolidayBaseRecurrenceModel Convert()
|
|
{
|
|
// Create object of the appropriate type
|
|
HolidayBaseRecurrenceModel result = null;
|
|
switch (this.periodOption)
|
|
{
|
|
case 1:
|
|
result = new HolidayDailyRecurrenceModel();
|
|
break;
|
|
case 2:
|
|
result = new HolidayWeeklyRecurrenceModel();
|
|
break;
|
|
case 3:
|
|
result = new HolidayMonthlyRecurrenceModel();
|
|
break;
|
|
case 4:
|
|
result = new HolidayYearlyRecurrenceModel();
|
|
break;
|
|
}
|
|
|
|
if (result != null)
|
|
// Assign values to created object
|
|
result.Assign(this);
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Base model for different types of the Holiday recurrences
|
|
/// </summary>
|
|
public abstract class HolidayBaseRecurrenceModel
|
|
{
|
|
public enum RecurrencePeriod
|
|
{
|
|
None = 0,
|
|
Daily = 1,
|
|
Weekly = 2,
|
|
Monthly = 3,
|
|
Yearly = 4
|
|
}
|
|
public enum RecurrenceLimits
|
|
{
|
|
NotLimited = 1,
|
|
AmountOfRepeat = 2,
|
|
EndDate = 3
|
|
}
|
|
|
|
protected const string C_JSON_PARSE_ERROR_MESSAGE = "Error parsing of the holiday recurrence json: {0}";
|
|
protected const string C_DATES_CALCULATION_ERROR_MESSAGE = "Unable to calculate recurrence dates: {0}";
|
|
|
|
private static Dictionary<int, HolidayBaseRecurrenceModel.RecurrencePeriod> periods =
|
|
new Dictionary<int, HolidayBaseRecurrenceModel.RecurrencePeriod>()
|
|
{
|
|
{1, RecurrencePeriod.Daily },
|
|
{2, RecurrencePeriod.Weekly },
|
|
{3, RecurrencePeriod.Monthly },
|
|
{4, RecurrencePeriod.Yearly }
|
|
};
|
|
|
|
public int NumberOfDays = 1;
|
|
public RecurrencePeriod Period = RecurrencePeriod.None;
|
|
public RecurrenceLimits LimitType = RecurrenceLimits.NotLimited;
|
|
public DateTime? StartDate;
|
|
public DateTime? EndDate;
|
|
public int? AmountOfRepeats;
|
|
|
|
public virtual void Assign(HolidayRecurrenceJsonModel source)
|
|
{
|
|
string message;
|
|
|
|
if (periods.ContainsKey(source.periodOption))
|
|
{
|
|
this.Clear();
|
|
this.Period = periods[source.periodOption];
|
|
}
|
|
else
|
|
{
|
|
message = String.Format("Recurrence type {0} is not supported", source.periodOption);
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, message);
|
|
throw new NotSupportedException(message);
|
|
}
|
|
|
|
if (source.StartDate.HasValue)
|
|
{
|
|
this.StartDate = Utils.ConvertFromUnixDate(source.StartDate.Value).Date;
|
|
}
|
|
else
|
|
{
|
|
if (!String.IsNullOrEmpty(source.range_start_picker))
|
|
{
|
|
DateTime parsedDate;
|
|
if (DateTime.TryParseExact(source.range_start_picker, "M/d/yyyy", CultureInfo.CurrentCulture,
|
|
DateTimeStyles.None, out parsedDate))
|
|
{
|
|
this.StartDate = parsedDate;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Parse iteration limits
|
|
if (source.rangeOption.HasValue)
|
|
{
|
|
RecurrenceLimits limits;
|
|
if (!Enum.TryParse(source.rangeOption.ToString(), out limits))
|
|
{
|
|
message = String.Format("Type of the recurrence range limit {0} is not supported", source.rangeOption);
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, message);
|
|
throw new NotSupportedException(message);
|
|
}
|
|
|
|
this.LimitType = limits;
|
|
|
|
switch (this.LimitType)
|
|
{
|
|
case RecurrenceLimits.AmountOfRepeat:
|
|
// Iterations limited by count
|
|
if (source.Occurrences.HasValue)
|
|
{
|
|
this.AmountOfRepeats = source.Occurrences.Value;
|
|
}
|
|
break;
|
|
|
|
case RecurrenceLimits.EndDate:
|
|
// Iterations limited by dates
|
|
if (source.EndDate.HasValue)
|
|
{
|
|
this.EndDate = Utils.ConvertFromUnixDate(source.EndDate.Value).Date;
|
|
}
|
|
else
|
|
{
|
|
if (!String.IsNullOrEmpty(source.range_picker))
|
|
{
|
|
DateTime parsedDate;
|
|
if (DateTime.TryParseExact(source.range_picker, "M/d/yyyy", CultureInfo.CurrentCulture,
|
|
DateTimeStyles.None, out parsedDate))
|
|
{
|
|
this.EndDate = parsedDate;
|
|
}
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public virtual void Clear()
|
|
{
|
|
this.Period = RecurrencePeriod.None;
|
|
this.LimitType = RecurrenceLimits.NotLimited;
|
|
this.StartDate = null;
|
|
this.EndDate = null;
|
|
this.AmountOfRepeats = null;
|
|
}
|
|
|
|
public abstract List<DateTime> GetDates(DateTime periodStart, DateTime periodEnd);
|
|
|
|
protected void ValidateDates(DateTime periodStart, DateTime periodEnd)
|
|
{
|
|
if (periodStart > periodEnd)
|
|
throw new ArgumentException("Period Starting date must be less or equal to Period end date");
|
|
|
|
if (!this.StartDate.HasValue)
|
|
throw new Exception("Starting date of the recurrence must have value");
|
|
}
|
|
|
|
protected DateTime GetNthDayInMonth(int year, int month, HolidayModel.HolidayOccurrence occurence, DayOfWeek dw)
|
|
{
|
|
string message;
|
|
|
|
bool itemFound = false;
|
|
DateTime occurenceDate = new DateTime(year, month, 1);
|
|
|
|
if ((occurence == HolidayModel.HolidayOccurrence.FirstDayOfWeek) ||
|
|
(occurence == HolidayModel.HolidayOccurrence.SecondDayOfWeek) ||
|
|
(occurence == HolidayModel.HolidayOccurrence.ThirdDayOfWeek) ||
|
|
(occurence == HolidayModel.HolidayOccurrence.FourthDayOfWeek))
|
|
{
|
|
// Looking Nth day of week in current month
|
|
int counter = 0;
|
|
|
|
while (!itemFound && (occurenceDate.Month == month))
|
|
{
|
|
if (occurenceDate.DayOfWeek == dw)
|
|
counter++;
|
|
|
|
if (Convert.ToInt32(occurence) == counter)
|
|
// Nth day of week found
|
|
itemFound = true;
|
|
else
|
|
// Go to the next day to check
|
|
occurenceDate = occurenceDate.AddDays(1);
|
|
}
|
|
}
|
|
|
|
if (occurence == HolidayModel.HolidayOccurrence.LastDayOfWeek)
|
|
{
|
|
// Looking Nth day of week in current month
|
|
occurenceDate = new DateTime(year, month, 1).AddMonths(1).AddDays(-1);
|
|
|
|
while (!itemFound)
|
|
{
|
|
while (!itemFound && (occurenceDate.Month == month))
|
|
{
|
|
itemFound = (occurenceDate.DayOfWeek == dw);
|
|
|
|
if (!itemFound)
|
|
// Go to the prev day to check
|
|
occurenceDate = occurenceDate.AddDays(-1);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!itemFound)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Occurence of the holiday not found. Check the parameter 'EveryNthDayOfWeek' has correct value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
return occurenceDate;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns DateTime object by specified YMD. If params are not valid, returns the last day ot the month.
|
|
/// E.g. for 2016, 04, 31 returns 2016-04-30
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
protected DateTime GetValidDateByParts(int year, int month, int day)
|
|
{
|
|
if ((year < Constants.FISCAL_CALENDAR_MIN_DATE.Year) || (year > (Constants.FISCAL_CALENDAR_MAX_DATE.Year + 1)))
|
|
throw new ArgumentException("year");
|
|
|
|
if ((month < 1) || (month > 12))
|
|
throw new ArgumentException("month");
|
|
|
|
if ((day < 1) || (day > 31))
|
|
throw new ArgumentException("day");
|
|
|
|
if (day < 29)
|
|
return new DateTime(year, month, day);
|
|
|
|
DateTime tmpDate = new DateTime(year, month, 1);
|
|
int daysInMonths = tmpDate.AddMonths(1).AddDays(-1).Day;
|
|
|
|
if (day > daysInMonths)
|
|
return new DateTime(year, month, daysInMonths);
|
|
else
|
|
return new DateTime(year, month, day);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds specified amount of months to date and force sets the specified Day part of the date
|
|
/// </summary>
|
|
protected DateTime AddMonths(DateTime srcDate, int monthCount, int forceSetDay)
|
|
{
|
|
DateTime newDate = srcDate.AddMonths(monthCount);
|
|
return GetValidDateByParts(newDate.Year, newDate.Month, forceSetDay);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds specified amount of years to date and force sets the specified Day part of the date
|
|
/// </summary>
|
|
protected DateTime AddYears(DateTime srcDate, int yearCount, int forceSetDay)
|
|
{
|
|
DateTime newDate = srcDate.AddYears(yearCount);
|
|
return GetValidDateByParts(newDate.Year, newDate.Month, forceSetDay);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns date, which iterations are limited in future
|
|
/// </summary>
|
|
protected DateTime GetEndRecurrencesDate(DateTime calcRangeEndDate)
|
|
{
|
|
DateTime limit = calcRangeEndDate;
|
|
|
|
if (this.LimitType == RecurrenceLimits.EndDate)
|
|
{
|
|
if (!this.EndDate.HasValue)
|
|
{
|
|
string message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Parameter 'EndDate' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EndDate.Value < calcRangeEndDate)
|
|
limit = this.EndDate.Value;
|
|
}
|
|
|
|
return limit;
|
|
}
|
|
|
|
protected int GetMaxRecurrencesAmount()
|
|
{
|
|
string message;
|
|
|
|
if (this.LimitType == RecurrenceLimits.AmountOfRepeat)
|
|
{
|
|
if (!this.AmountOfRepeats.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Parameter 'AmountOfRepeats' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.AmountOfRepeats.Value < 1)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Parameter 'AmountOfRepeats' must have a positive value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
return this.AmountOfRepeats.Value;
|
|
}
|
|
else
|
|
return Int32.MaxValue;
|
|
}
|
|
|
|
protected void AddDatesToResult(List<DateTime> result, DateTime date, int daysInHoliday)
|
|
{
|
|
for (int index = 0; index < daysInHoliday; index++)
|
|
{
|
|
DateTime dateToAdd = date.AddDays(index);
|
|
result.Add(dateToAdd);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Model for daily recurrence of a Holiday
|
|
/// </summary>
|
|
public class HolidayDailyRecurrenceModel : HolidayBaseRecurrenceModel
|
|
{
|
|
public int? EveryNdays;
|
|
public bool EveryWeekday;
|
|
|
|
public override void Clear()
|
|
{
|
|
base.Clear();
|
|
this.EveryNdays = null;
|
|
this.EveryWeekday = false;
|
|
}
|
|
|
|
public override void Assign(HolidayRecurrenceJsonModel source)
|
|
{
|
|
string message;
|
|
base.Assign(source);
|
|
|
|
if (!source.dailyOption.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'dailyOption' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
switch (source.dailyOption.Value)
|
|
{
|
|
case 1:
|
|
// Repeat daily, every N days
|
|
if (!source.dailyDay.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'dailyDay' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
this.EveryNdays = source.dailyDay.Value;
|
|
this.EveryWeekday = false;
|
|
break;
|
|
|
|
case 2:
|
|
// Repeat daily, every weekday
|
|
this.EveryNdays = 1;
|
|
this.EveryWeekday = true;
|
|
break;
|
|
|
|
default:
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'dailyDay' has unsupported value");
|
|
throw new NotSupportedException(message);
|
|
}
|
|
}
|
|
|
|
public override List<DateTime> GetDates(DateTime periodStart, DateTime periodEnd)
|
|
{
|
|
// Note: The method calculates holiday occurence dates from startingPoint and to the future
|
|
// up to periodEnd.
|
|
// If startingPoint > periodStart, dates are calculated from startingPoint to periodEnd.
|
|
// If startingPoint < periodStart, dates are calculated from periodStart to periodEnd.
|
|
string message;
|
|
List<DateTime> result = new List<DateTime>();
|
|
this.ValidateDates(periodStart, periodEnd);
|
|
|
|
if (this.EveryWeekday)
|
|
{
|
|
// SA: Option in the UI form is hidden. Code left here for the case, that option may be set visible later
|
|
throw new NotImplementedException("Daily weekday recurrence is not implemented");
|
|
}
|
|
else
|
|
{
|
|
if (!this.EveryNdays.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNdays' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EveryNdays.Value <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNdays' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
int iterationsCount = this.GetMaxRecurrencesAmount();
|
|
int counter = 0;
|
|
|
|
DateTime baseDate = this.StartDate.Value;
|
|
DateTime periodStartCorrected = periodStart.Date;
|
|
DateTime periodEndCorrected = this.GetEndRecurrencesDate(periodEnd.Date).AddDays(1);
|
|
DateTime currentDate = baseDate;
|
|
|
|
while ((counter < iterationsCount) && (currentDate < periodEndCorrected))
|
|
{
|
|
if (currentDate >= periodStartCorrected)
|
|
{
|
|
this.AddDatesToResult(result, currentDate, this.NumberOfDays);
|
|
counter++;
|
|
}
|
|
|
|
currentDate = currentDate.AddDays(this.EveryNdays.Value);
|
|
}
|
|
}
|
|
|
|
return result.Distinct().ToList();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Model for weekly recurrence of a Holiday
|
|
/// </summary>
|
|
public class HolidayWeeklyRecurrenceModel : HolidayBaseRecurrenceModel
|
|
{
|
|
public int? EveryNweeks;
|
|
public List<DayOfWeek> WeekDays;
|
|
|
|
public override void Clear()
|
|
{
|
|
base.Clear();
|
|
this.EveryNweeks = null;
|
|
this.WeekDays = null;
|
|
}
|
|
public override void Assign(HolidayRecurrenceJsonModel source)
|
|
{
|
|
string message;
|
|
base.Assign(source);
|
|
|
|
if (!source.weeklyWeeks.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'weeklyWeeks' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
this.EveryNweeks = source.weeklyWeeks.Value;
|
|
|
|
this.WeekDays = new List<DayOfWeek>();
|
|
if (source.Sunday.HasValue && source.Sunday.Value) this.WeekDays.Add(DayOfWeek.Sunday);
|
|
if (source.Monday.HasValue && source.Monday.Value) this.WeekDays.Add(DayOfWeek.Monday);
|
|
if (source.Tuesday.HasValue && source.Tuesday.Value) this.WeekDays.Add(DayOfWeek.Tuesday);
|
|
if (source.Wednesday.HasValue && source.Wednesday.Value) this.WeekDays.Add(DayOfWeek.Wednesday);
|
|
if (source.Thursday.HasValue && source.Thursday.Value) this.WeekDays.Add(DayOfWeek.Thursday);
|
|
if (source.Friday.HasValue && source.Friday.Value) this.WeekDays.Add(DayOfWeek.Friday);
|
|
if (source.Saturday.HasValue && source.Saturday.Value) this.WeekDays.Add(DayOfWeek.Saturday);
|
|
}
|
|
|
|
public override List<DateTime> GetDates(DateTime periodStart, DateTime periodEnd)
|
|
{
|
|
// Note: The method calculates holiday occurence dates from startingPoint and to the future
|
|
// up to periodEnd.
|
|
// If startingPoint > periodStart, dates are calculated from startingPoint to periodEnd.
|
|
// If startingPoint < periodStart, dates are calculated from periodStart to periodEnd.
|
|
string message;
|
|
List<DateTime> result = new List<DateTime>();
|
|
this.ValidateDates(periodStart, periodEnd);
|
|
|
|
if (!this.EveryNweeks.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNweeks' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EveryNweeks.Value <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNweeks' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if ((this.WeekDays == null) || (this.WeekDays.Count < 1))
|
|
// Week days for recurrence are not set
|
|
return result;
|
|
|
|
int iterationsCount = this.GetMaxRecurrencesAmount();
|
|
int counter = 0;
|
|
|
|
DateTime baseDate = this.StartDate.Value;
|
|
DateTime periodStartCorrected = periodStart.Date;
|
|
DateTime periodEndCorrected = this.GetEndRecurrencesDate(periodEnd.Date).AddDays(1);
|
|
DateTime currentDate;
|
|
DayOfWeek currentDayOfWeek;
|
|
|
|
// Add holiday in the specified Start and End Dates
|
|
for (int index = 0; index < this.NumberOfDays; index++)
|
|
{
|
|
currentDate = baseDate.AddDays(index);
|
|
if ((currentDate >= periodStartCorrected) && (currentDate < periodEndCorrected))
|
|
result.Add(currentDate);
|
|
}
|
|
|
|
// Get first day (Sun) of the week, Starting date belongs to
|
|
currentDate = baseDate;
|
|
while (currentDate.DayOfWeek != DayOfWeek.Sunday)
|
|
currentDate = currentDate.AddDays(-1);
|
|
|
|
// Skip specified amount of weeks after initial holiday (Start to End Dates)
|
|
DateTime firstWeekDay = currentDate.AddDays(7 * this.EveryNweeks.Value);
|
|
|
|
// Calculate other holiday dates through recurrence
|
|
while ((counter < iterationsCount) && (currentDate < periodEndCorrected))
|
|
{
|
|
// Get holidays from current week by specified days of week
|
|
currentDate = firstWeekDay;
|
|
currentDayOfWeek = currentDate.DayOfWeek;
|
|
|
|
while ((counter < iterationsCount) &&
|
|
((currentDate == firstWeekDay) || (currentDayOfWeek != DayOfWeek.Sunday)))
|
|
{
|
|
if (this.WeekDays.Contains(currentDayOfWeek) &&
|
|
(currentDate >= periodStartCorrected) && (currentDate < periodEndCorrected))
|
|
{
|
|
this.AddDatesToResult(result, currentDate, this.NumberOfDays);
|
|
counter++;
|
|
}
|
|
|
|
currentDate = currentDate.AddDays(1);
|
|
currentDayOfWeek = currentDate.DayOfWeek;
|
|
}
|
|
|
|
// Skip specified amount of weeks
|
|
firstWeekDay = firstWeekDay.AddDays(7 * this.EveryNweeks.Value);
|
|
}
|
|
|
|
return result.Distinct().ToList();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Model for monthly recurrence of a Holiday
|
|
/// </summary>
|
|
public class HolidayMonthlyRecurrenceModel : HolidayBaseRecurrenceModel
|
|
{
|
|
public bool EveryNdateOfNmonths;
|
|
public bool EveryXweekdayOfNmonths;
|
|
|
|
public byte? EveryNdate;
|
|
public byte? EveryNmonth;
|
|
|
|
public HolidayModel.HolidayOccurrence? EveryNthDayOfWeek;
|
|
public DayOfWeek? EveryDayOfWeek;
|
|
|
|
public override void Assign(HolidayRecurrenceJsonModel source)
|
|
{
|
|
string message;
|
|
base.Assign(source);
|
|
|
|
if (!source.monthlyOption.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyOption' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
switch (source.monthlyOption.Value)
|
|
{
|
|
case 1:
|
|
// Repeat monthly, date N of every N month(s)
|
|
if (!source.monthlyDate.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyDate' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!source.monthlyMonths.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyMonths' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
this.EveryNdateOfNmonths = true;
|
|
this.EveryNdate = (byte)source.monthlyDate.Value;
|
|
this.EveryNmonth = (byte)source.monthlyMonths.Value;
|
|
break;
|
|
|
|
case 2:
|
|
// Repeat monthly, the First Monday of every N month
|
|
if (String.IsNullOrEmpty(source.monthlyOccurrence))
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyOccurrence' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(source.monthlyWeek))
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyWeek' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!source.monthlyMonth.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyMonth' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
this.EveryXweekdayOfNmonths = true;
|
|
|
|
try
|
|
{
|
|
HolidayModel.HolidayOccurrence monthlyOccurrenceParsed =
|
|
(HolidayModel.HolidayOccurrence)Enum.Parse(typeof(HolidayModel.HolidayOccurrence),
|
|
source.monthlyOccurrence, true);
|
|
this.EveryNthDayOfWeek = monthlyOccurrenceParsed;
|
|
}
|
|
catch
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyOccurrence' has unsupported value");
|
|
throw new NotSupportedException(message);
|
|
}
|
|
|
|
try
|
|
{
|
|
DayOfWeek monthlyWeekParsed = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), source.monthlyWeek, true);
|
|
this.EveryDayOfWeek = monthlyWeekParsed;
|
|
}
|
|
catch
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyWeek' has unsupported value");
|
|
throw new NotSupportedException(message);
|
|
}
|
|
|
|
this.EveryNmonth = (byte)source.monthlyMonth.Value;
|
|
break;
|
|
|
|
default:
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'monthlyOption' has unsupported value");
|
|
throw new NotSupportedException(message);
|
|
}
|
|
}
|
|
|
|
public override List<DateTime> GetDates(DateTime periodStart, DateTime periodEnd)
|
|
{
|
|
// Note: The method calculates holiday occurence dates from startingPoint and to the future
|
|
// up to periodEnd.
|
|
// If startingPoint > periodStart, dates are calculated from startingPoint to periodEnd.
|
|
// If startingPoint < periodStart, dates are calculated from periodStart to periodEnd.
|
|
this.ValidateDates(periodStart, periodEnd);
|
|
|
|
List<DateTime> result = new List<DateTime>();
|
|
DateTime periodStartCorrected = periodStart.Date;
|
|
DateTime periodEndCorrected = this.GetEndRecurrencesDate(periodEnd.Date).AddDays(1);
|
|
DateTime baseDate = this.StartDate.Value;
|
|
|
|
// Add holiday in the specified Start and End Dates
|
|
for (int index = 0; index < this.NumberOfDays; index++)
|
|
{
|
|
DateTime currentDate = baseDate.AddDays(index);
|
|
if ((currentDate >= periodStartCorrected) && (currentDate < periodEndCorrected))
|
|
result.Add(currentDate);
|
|
}
|
|
|
|
if (this.EveryNdateOfNmonths)
|
|
{
|
|
GetDatesForNdateNMonths(periodStartCorrected, periodEndCorrected, result);
|
|
return result.Distinct().ToList();
|
|
}
|
|
|
|
if (this.EveryXweekdayOfNmonths)
|
|
{
|
|
GetDatesForXweekdayNMonths(periodStartCorrected, periodEndCorrected, result);
|
|
}
|
|
|
|
return result.Distinct().ToList();
|
|
}
|
|
|
|
protected void GetDatesForNdateNMonths(DateTime periodStartCorrected, DateTime periodEndCorrected, List<DateTime> result)
|
|
{
|
|
#region Validation
|
|
|
|
string message;
|
|
|
|
if (!this.EveryNdate.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNdate' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!this.EveryNmonth.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNmonth' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EveryNdate.Value <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNdate' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EveryNmonth.Value <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNmonth' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
int iterationsCount = this.GetMaxRecurrencesAmount();
|
|
int counter = 0;
|
|
|
|
// Calculate start date for recurrence
|
|
DateTime currentDate = this.StartDate.Value;
|
|
|
|
// Skip specified amount of months
|
|
currentDate = this.AddMonths(currentDate, this.EveryNmonth.Value, this.EveryNdate.Value);
|
|
|
|
//if (this.StartDate.Value.Day > this.EveryNdate.Value)
|
|
// currentDate = this.AddMonths(currentDate, 1, this.EveryNdate.Value);
|
|
|
|
// Calculate other holiday dates through recurrence
|
|
while ((counter < iterationsCount) && (currentDate < periodEndCorrected))
|
|
{
|
|
if ((currentDate >= periodStartCorrected) && (currentDate < periodEndCorrected))
|
|
{
|
|
this.AddDatesToResult(result, currentDate, this.NumberOfDays);
|
|
counter++;
|
|
}
|
|
|
|
currentDate = this.AddMonths(currentDate, this.EveryNmonth.Value, this.EveryNdate.Value);
|
|
}
|
|
}
|
|
|
|
protected void GetDatesForXweekdayNMonths(DateTime periodStartCorrected, DateTime periodEndCorrected, List<DateTime> result)
|
|
{
|
|
#region Validation
|
|
|
|
string message;
|
|
|
|
if (!this.EveryNthDayOfWeek.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNthDayOfWeek' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!this.EveryDayOfWeek.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryDayOfWeek' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!this.EveryNmonth.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNmonth' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EveryNmonth.Value <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNmonth' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
int iterationsCount = this.GetMaxRecurrencesAmount();
|
|
int counter = 0;
|
|
|
|
// Calculate start date for recurrence
|
|
DateTime currentDate = this.StartDate.Value;
|
|
//DateTime currentDate =
|
|
// this.GetValidDateByParts(this.StartDate.Value.Year, this.StartDate.Value.Month, this.EveryNdate.Value);
|
|
|
|
// Skip specified amount of months
|
|
currentDate = this.AddMonths(currentDate, this.EveryNmonth.Value, 1);
|
|
|
|
// Go through other dates via recurrence
|
|
// Looking for the first holiday occurence in month, the starting point belongs to
|
|
DateTime occurenceDate = this.GetNthDayInMonth(currentDate.Year, currentDate.Month,
|
|
this.EveryNthDayOfWeek.Value, this.EveryDayOfWeek.Value);
|
|
|
|
while ((counter < iterationsCount) && (occurenceDate < periodEndCorrected))
|
|
{
|
|
if (occurenceDate >= periodStartCorrected)
|
|
{
|
|
this.AddDatesToResult(result, occurenceDate, this.NumberOfDays);
|
|
counter++;
|
|
}
|
|
|
|
DateTime nextOccurenceDate = this.AddMonths(occurenceDate, this.EveryNmonth.Value, occurenceDate.Day);
|
|
occurenceDate = this.GetNthDayInMonth(nextOccurenceDate.Year, nextOccurenceDate.Month,
|
|
this.EveryNthDayOfWeek.Value, this.EveryDayOfWeek.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Model for yearly recurrence of a Holiday
|
|
/// </summary>
|
|
public class HolidayYearlyRecurrenceModel : HolidayBaseRecurrenceModel
|
|
{
|
|
public int EveryNyears;
|
|
|
|
public byte? EveryDateOrdinalNum;
|
|
public byte? EveryMonthOrdinalNum;
|
|
|
|
public HolidayModel.HolidayOccurrence? EveryNthDayOfWeek;
|
|
public DayOfWeek? EveryDayOfWeek;
|
|
|
|
public override void Clear()
|
|
{
|
|
base.Clear();
|
|
this.EveryNyears = 0;
|
|
this.EveryDateOrdinalNum = null;
|
|
this.EveryMonthOrdinalNum = null;
|
|
this.EveryNthDayOfWeek = null;
|
|
this.EveryDayOfWeek = null;
|
|
}
|
|
public override void Assign(HolidayRecurrenceJsonModel source)
|
|
{
|
|
string message;
|
|
base.Assign(source);
|
|
|
|
if (!source.yearlyYear.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyYear' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!source.yearlyOption.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyOption' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
this.EveryNyears = source.yearlyYear.Value;
|
|
|
|
switch (source.yearlyOption.Value)
|
|
{
|
|
case 1:
|
|
if (!source.yearlyMonth.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyMonth' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!source.yearlyMonthNumber.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyMonthNumber' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
this.EveryMonthOrdinalNum = (byte)source.yearlyMonth.Value;
|
|
this.EveryDateOrdinalNum = (byte)source.yearlyMonthNumber.Value;
|
|
break;
|
|
|
|
case 2:
|
|
if (String.IsNullOrEmpty(source.yearlyOccurrence))
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyOccurrence' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (String.IsNullOrEmpty(source.yearlyWeek))
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyWeek' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!source.yearlyMonth2.HasValue)
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyMonth2' must have value");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
try
|
|
{
|
|
HolidayModel.HolidayOccurrence yearlyOccurrenceParsed =
|
|
(HolidayModel.HolidayOccurrence)Enum.Parse(typeof(HolidayModel.HolidayOccurrence),
|
|
source.yearlyOccurrence, true);
|
|
this.EveryNthDayOfWeek = yearlyOccurrenceParsed;
|
|
}
|
|
catch
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyOccurrence' has unsupported value");
|
|
throw new NotSupportedException(message);
|
|
}
|
|
|
|
try
|
|
{
|
|
DayOfWeek yearlyWeekParsed = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), source.yearlyWeek, true);
|
|
this.EveryDayOfWeek = yearlyWeekParsed;
|
|
}
|
|
catch
|
|
{
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyWeek' has unsupported value");
|
|
throw new NotSupportedException(message);
|
|
}
|
|
|
|
this.EveryMonthOrdinalNum = (byte)source.yearlyMonth2.Value;
|
|
break;
|
|
|
|
default:
|
|
message = String.Format(C_JSON_PARSE_ERROR_MESSAGE, "Key 'yearlyOption' has unsupported value");
|
|
throw new NotSupportedException(message);
|
|
}
|
|
}
|
|
|
|
public override List<DateTime> GetDates(DateTime periodStart, DateTime periodEnd)
|
|
{
|
|
// Note: The method calculates holiday occurence dates from startingPoint and to the future
|
|
// up to periodEnd.
|
|
// If startingPoint > periodStart, dates are calculated from startingPoint to periodEnd.
|
|
// If startingPoint < periodStart, dates are calculated from periodStart to periodEnd.
|
|
this.ValidateDates(periodStart, periodEnd);
|
|
string message;
|
|
|
|
if (this.EveryNyears <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNyears' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
List<DateTime> result = new List<DateTime>();
|
|
DateTime periodStartCorrected = periodStart.Date;
|
|
DateTime periodEndCorrected = this.GetEndRecurrencesDate(periodEnd.Date).AddDays(1);
|
|
DateTime baseDate = this.StartDate.Value;
|
|
|
|
// Add holiday in the specified Start and End Dates
|
|
for (int index = 0; index < this.NumberOfDays; index++)
|
|
{
|
|
DateTime currentDate = baseDate.AddDays(index);
|
|
if ((currentDate >= periodStartCorrected) && (currentDate < periodEndCorrected))
|
|
result.Add(currentDate);
|
|
}
|
|
|
|
if (this.EveryDateOrdinalNum.HasValue && EveryMonthOrdinalNum.HasValue)
|
|
{
|
|
GetDatesForNdateInMonth(periodStartCorrected, periodEndCorrected, result);
|
|
return result.Distinct().ToList();
|
|
}
|
|
|
|
if (this.EveryNthDayOfWeek.HasValue && EveryDayOfWeek.HasValue && EveryMonthOrdinalNum.HasValue)
|
|
{
|
|
GetDatesForXweekdayInMonth(periodStartCorrected, periodEndCorrected, result);
|
|
}
|
|
|
|
return result.Distinct().ToList();
|
|
}
|
|
|
|
protected void GetDatesForNdateInMonth(DateTime periodStartCorrected, DateTime periodEndCorrected, List<DateTime> result)
|
|
{
|
|
#region Validation
|
|
|
|
string message;
|
|
|
|
if (!this.EveryDateOrdinalNum.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryDateOrdinalNum' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!this.EveryMonthOrdinalNum.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryMonthOrdinalNum' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EveryDateOrdinalNum.Value <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryDateOrdinalNum' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (this.EveryMonthOrdinalNum.Value <= 0)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryMonthOrdinalNum' must be a positive number");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
int iterationsCount = this.GetMaxRecurrencesAmount();
|
|
int counter = 0;
|
|
|
|
// Skip specified number of months form start date
|
|
DateTime currentDate = this.GetValidDateByParts(this.StartDate.Value.Year, this.EveryMonthOrdinalNum.Value,
|
|
this.EveryDateOrdinalNum.Value);
|
|
currentDate = this.AddYears(currentDate, this.EveryNyears, this.EveryDateOrdinalNum.Value);
|
|
|
|
// Calculate other holidays through recurrence reules
|
|
while ((counter < iterationsCount) && (currentDate < periodEndCorrected))
|
|
{
|
|
if ((currentDate >= periodStartCorrected) && (currentDate < periodEndCorrected))
|
|
{
|
|
this.AddDatesToResult(result, currentDate, this.NumberOfDays);
|
|
counter++;
|
|
}
|
|
|
|
currentDate = this.AddYears(currentDate, this.EveryNyears, this.EveryDateOrdinalNum.Value);
|
|
}
|
|
}
|
|
|
|
protected void GetDatesForXweekdayInMonth(DateTime periodStartCorrected, DateTime periodEndCorrected, List<DateTime> result)
|
|
{
|
|
#region Validation
|
|
|
|
string message;
|
|
|
|
if (!this.EveryNthDayOfWeek.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryNthDayOfWeek' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!this.EveryDayOfWeek.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryDayOfWeek' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
if (!this.EveryMonthOrdinalNum.HasValue)
|
|
{
|
|
message = String.Format(C_DATES_CALCULATION_ERROR_MESSAGE, "Parameter 'EveryMonthOrdinalNum' is not set");
|
|
throw new Exception(message);
|
|
}
|
|
|
|
#endregion
|
|
|
|
int iterationsCount = this.GetMaxRecurrencesAmount();
|
|
int counter = 0;
|
|
|
|
// Skip time from Starting Date for Holiday
|
|
DateTime occurenceDate = this.StartDate.Value;
|
|
occurenceDate = occurenceDate.AddYears(this.EveryNyears);
|
|
|
|
// Looking for the first holiday occurence in month, the starting point belongs to
|
|
occurenceDate = this.GetNthDayInMonth(occurenceDate.Year, this.EveryMonthOrdinalNum.Value,
|
|
this.EveryNthDayOfWeek.Value, this.EveryDayOfWeek.Value);
|
|
|
|
while ((counter < iterationsCount) && (occurenceDate < periodEndCorrected))
|
|
{
|
|
if (occurenceDate >= periodStartCorrected)
|
|
{
|
|
this.AddDatesToResult(result, occurenceDate, this.NumberOfDays);
|
|
counter++;
|
|
}
|
|
|
|
DateTime nextOccurenceDate = occurenceDate.AddYears(this.EveryNyears);
|
|
occurenceDate = this.GetNthDayInMonth(nextOccurenceDate.Year, nextOccurenceDate.Month,
|
|
this.EveryNthDayOfWeek.Value, this.EveryDayOfWeek.Value);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
} |