Create an absolute or relative time period class in C# - c#

I want to create some class that can calculate absolute or relative time period.
Meaning: the constructor will get either time period and units ( 3 weeks ) or two DateTime start and finish time. I have wrote the following code :
public class TimePeriod
{
public State TimePerriodState { get; private set; }
public RelativeTime RelativeTimePeriod { get; private set; }
public int UnitsOfTime { get; private set; }
public DateTime? StartTime { get; private set; }
public DateTime? EndTime { get; private set; }
public TimePeriod(RelativeTime relativeTime, int unitsOfTime)
{
TimePerriodState = State.Absolute;
RelativeTimePeriod = relativeTime;
UnitsOfTime = unitsOfTime;
}
public TimePeriod(DateTime startTime , DateTime endTime)
{
TimePerriodState = State.Relative;
StartTime = startTime;
EndTime = endTime;
}
public enum State
{
None,
Absolute,
Relative
}
public enum RelativeTime
{
None,
Hours,
Days,
Weeks,
Months,
Year
}
}
But I don't like that the usage is base on the state.
In the end the data will appear in the UI as two different controls. Is there a better way of making the API little better? Maybe pass in the data and calculate the dateTime on the fly or some thing like that?
UPDATE :
the usage is relative to any time its being used. meaning enter a timespan is not possible. lets say we need fo have the relative time prior 10 days from now or fixed days from X to Y
The TimeSpan will be calculated in a different BL class (this is a POCO class)

You're basically trying to store the same data twice.
You can define any period of time by either:
A startdate and enddate, duration can be calculated by subtraction.
A startdate and duration (TimeSpan), Enddate can be calculated by adding the two.
You could do it with a duration and enddate but that's making things harder.
What you want to do is pick one way of storing it. If you want it the other way, write a function to calculate it. E.g:
public TimePeriod(DateTime startTime , DateTime endTime)
{
StartTime = startTime;
EndTime = endTime;
}
public TimePeriod(DateTime startTime , TimeSpan length)
{
StartTime = startTime;
EndTime = startTime + length;
}
public TimeSpan GetDuration()
{
return EndTime - StartTime;
}
//Else you can just get the StartTime or EndTime variable (DateTime)

I think you have a problem with your terminology / domain understanding. Time periods are always relative by definition. An absolute time period would be a fixed date!
Can you instead use the TimeSpan struct which is part of the framework? You can then 'decorate' it in some way to indicate where this time-span was calculated from.

First of all I doubt that using some sort of UnitsOfTime is a very good idea. Try use TimeSpan instead and UI will show weeks or months. Try something like this:
public class TimePeriod
{
private readonly DateTime? _startDate;
private readonly DateTime? _endDate;
private readonly TimeSpan _timeSpan;
public TimePeriod(DateTime startDate, DateTime endDate)
{
if (_startDate > _endDate)
{
throw new ArgumentException();
}
_startDate = startDate;
_endDate = endDate;
_timeSpan = endDate - startDate;
}
public TimePeriod(TimeSpan timeSpan)
{
_timeSpan = timeSpan;
}
public TimeSpan TimeSpan
{
get { return _timeSpan; }
}
public DateTime? StartDate
{
get { return _startDate; }
}
public DateTime? EndDate
{
get { return _endDate; }
}
public bool IsAbsolute
{
get { return _startDate.HasValue; }
}
}
Also could you explain the reason why you need this class? It could help.

Related

Comparing a DateTime Value to any value on a list of DateTimes in C#

I have an Appointment Scheduling Application where you set an Appointment Time and when you set that time, I want to check and see if their appointment is overlapping any other Appointment times and if So then I want to prevent them from scheduling the appointment.
I have 2 lists containing the appointment start and End Times:
public List<DateTime> AppointmentStartTimes = new List<DateTime>();
public List<DateTime> AppointmentEndTimes = new List<DateTime>();
I want them to not be able to schedule an appointment if the start time is in between any of the values on either of these lists, How would I compare the time to the values on both lists thanks?
Rather than using two lists, a cleaner / more extensible approach would be to create an Appointment class:
class Appointment
{
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public bool ConflictsWith(Appointment proposed)
{
return StartTime < proposed.EndTime && proposed.StartTime < EndTime;
}
}
Then you can have a single list of appointments:
public List<Appointment> Appointments = new List<Appointment>();
And you can check for conflicts as so:
Appointment proposed = //...
bool conflicts = Appointments.Any(appointment => appointment.ConflictsWith(proposed));
My advice would be to create a class Appointment, which has a.o. a TimeFrame. A TimeFrame has a StartTime / EndTime / Duration:
interface ITimeFrame
{
DateTime StartTime {get;}
DateTime EndTime {get;}
TimeSpan Duration {get;}
}
Possible implementations:
class TimeFrame : ITimeFrame
{
public DateTime StartTime {get; set;}
public DateTime EndTime {get; set;}
public TimeSpan Duration => EndTime - StartTime;
}
But you could also choose for an Implementation where the EndTime => StartTime + Duration
interface AppointMent
{
int Id {get; }
IReadOnlyCollection<Person> Attendees {get;}
ITimeFrame TimeFrame {get; }
}
You want to know whether two Appointment overlap. I create it as extension methods of ITimeFrame and IAppointment:
public static bool Overlaps(this ITimeFrame timeFrameA, ITimeFrame timeFrameB)
{
// assuming startTime of a TimeFrame is always smaller than EndTime
// there is an overlap if
// frameA starts before frameB ends AND frameA ends after frameB starts
return timeFrameA.StartTime < timeFrameB.EndTime
&& timeFrameA.EndTime > timeFrame.StartTime;
}
public static bool Overlaps(this IAppointment appointmentA, IAppontment appointmentB)
{
return appointmentA.TimeFrame.Overlaps(appointmentB.TimeFrame);
}
Usage:
IAppointment a = ...
IAppointment b = ...
bool overlaps = a.Overlaps(b);
For LINQ we do the same with sequences:
public static bool Overlaps(
this IEnumerable<IAppointment> appointments,
IAppointment otherAppointment)
{
return appointments.Where(appointment => appointment.Overlaps(otherAppointment)).Any();
}
Usage:
IEnumerable<IAppointment> existingAppointment = ...
IAppointment requestedAppointment = ...
var hasOverlappingAppointments = existingAppointment
.Overlaps(requestedAppointment)
.Any();
if (hasOverlappingAppointments)
{
WriteMessage("Can't make this appointment, it overlaps with existing appointments");
}
else
{
AcceptAppointment(requestedAppointment);
}

AutoMapper configuration to use local time for all DateTime properties

Say I have 2 classes with the same set of properties:
public class MyDto
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
}
public class MyViewModel
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
}
I want to map with AutoMapper, adjusting the UTC date of the input class to local time of the output class, e.g., granted I am in UK where UTC offset currently is 1h:
var input = new MyDto {Id = 1, CreatedOn = DateTime.Parse("01-01-2015 14:30")};
var output = Mapper.Map<MyViewModel>(input); // output.CreatedOn = "01-01-2015 15:30"
Can I cofigure AutoMapper to this automatically for all DateTime properties?
N.B. to adjust the time I use DateTime.SpecifyKind(value, DateTimeKind.Utc)
You can create a custom type converter:
public class CustomDateTimeConverter : ITypeConverter<DateTime, DateTime> {
public DateTime Convert(ResolutionContext context) {
var inputDate = (DateTime) context.SourceValue;
var timeInUtc = DateTime.SpecifyKind(inputDate, DateTimeKind.Utc);
return TimeZoneInfo.ConvertTime(timeInUtc, TimeZoneInfo.Local);
}
}
This will make AutoMapper perform the conversion from UTC to local time for every mapping between two DateTime properties.

calculating age in c# simple yet

I have a property DateOfBirth and a property Age.
DateOfBirth is DateTime datatype and Age is int datatype.
I want to calculate the person's age inside the constructor, and I have:
private int CalculateAge(DateTime birthDate, DateTime now)
{
int age = now.Year - birthDate.Year;
if (now.Month < birthDate.Month || (now.Month == birthDate.Month && now.Day < birthDate.Day))
{
age--;
}
return age;
}
public virtual DateTime? Dob { get; set; }
public virtual int Age { get; set; }
public MyObject()
{
Age = CalculateAge(Dob, DateTime.Now);
}
At compile time I'm getting the following errors:
The best overloaded method match for ... has some invalid arguments
and
cannot convert from 'System.DateTime?' to System.DateTime
The best overloaded method match for .... has some invalid arguments and cannot convert from 'System.DateTime?' to System.DateTime
So what did you try to solve this? The error is pretty clear: you're passing a System.DateTime? parameter to a function that accepts a System.DateTime.
To fix it, either change the method signature
CalculateAge(DateTime? birthDate, DateTime now)
{
if (!birthDate.HasValue)
{
return -1; // ?
}
}
But as you see, that's quite useless. So change the call:
if (Dob.HasValue)
{
Age = CalculateAge(Dob.Value, DateTime.Now);
}
Ultimately you'd just want to use a property for this:
public virtual int Age {
get
{
if (!Dob.HasValue)
{
throw new Exception(); // ?
return -1; // ?
}
return CalculateAge(Dob.Value);
}
}
As you see it doesn't matter where you solve this: you just have to check somewhere whether the nullable (?) date of birth contains a value.
You should pass a DateTime not a nullable DateTime
Age = CalculateAge((Dob.HasValue ? Dob.Value : DateTime.Now), DateTime.Now);
Or change the receiving method
private int CalculateAge(DateTime? birthDate, DateTime now)
and apply all the check needed to avoid NullReferenceExceptions
You CalculateAge method accepts a DateTime parameter, and you are passing it a DateTime? (nullable DateTime). You must change one of these, or cast to a DateTime.
Futhermore, there is no real reason for the second parameter, as DateTime.Now can be calculated inside the method.
Thirdly, see similar questions on SO for calculating age: Calculate age in C#
Look at your method declaration
private int CalculateAge(DateTime birthDate, DateTime now)
And DateOfBirth declaration
public virtual DateTime? Dob { get; set; }
You cannot use nullable DateTime property as a first parameter. Change declaration to
private int CalculateAge(DateTime? birthDate, DateTime now)
or remove nullability from Dob property
public virtual DateTime Dob { get; set; }
You can use
public static int GetAge(DateTime birthDate)
{
DateTime n = DateTime.Now; // To avoid a race condition around midnight
int age = n.Year - birthDate.Year;
if (n.Month < birthDate.Month || (n.Month == birthDate.Month && n.Day < birthDate.Day))
age--;
return age;
}
use private int CalculateAge(DateTime? birthDate, DateTime now)
instead of
private int CalculateAge(DateTime birthDate, DateTime now)
Use TimeSpan to get the difference between the two dates as mentioned here:
private int CalculateAge(DateTime birthDate, DateTime now)
{
TimeSpan span = now.Subtract(birthDate);
return (int)span.TotalDays / 365;
}
Change method definition and check if birthDate has value (is not null)
private int CalculateAge(DateTime? birthDate, DateTime now)
{
if(birthDate.HasValue)
{
int age = now.Year - birthDate.Year;
if (now.Month < birthDate.Month || (now.Month == birthDate.Month && now.Day < birthDate.Day))
{
age--;
}
return age;
}
else
return 0;
}
You will have to cast your DateTime? to DateTime like so
(DateTime)Dob
But why bother making Dob nullable in the first place if you are not handling the possibility of a null date anywhere in your code?

Convert TimeSpan.TotalMilliseconds to datetime and format it as hour:minute

I kinda confuse about the date time conversion
in my model i have defined
public DateTime? Sent { get; set; }
public DateTime? Reply { get; set; }
public double ResponseTime { get; set; }
in linq part i am using
ResponseTime = (u.Reply - u.sent).TotalMilliseconds
which is not formated and displayed like this 979809803
I want to know how do i convert it to datetime format, and eventually will display the format as hour:minute, for instance 2:45 between the date sent and date reply.
Just return the TimeSpan and call .ToString("hh:mm").
TimeSpan.ToString
public TimeSpan ResponseTime { get; set; }
//usage in LINQ
ResponseTime = (u.Reply - u.Sent)
When displaying...
value.ResponseTime.ToString("hh:mm")
Subtracting a DateTime from a DateTime yields a TimeSpan. Have a look at the TimeSpan.ToString Method (String) to see how you can custom format the value.
You can change ResponseTime back to a TimeSpan and from there to a string.
TimeSpan ResponseTimeSpan = new TimeSpan(0, 0, 0, (int)ResponseTime);
string ResponseTimeDisplay = ResponseTimeSpan.ToString();

Just Date or Time

I'm just wondering ..
I have and object like this
public class Entry{
public DateTime? Date { get; set;} // This is just Date
public DateTime? StartTime { get; set; } //This is just Time
public TimeSpan Duration { get; set; } //Time spent on entry
}
Is there a more appropriate type than DateTime or better strategy to handling just Time and Date? Without the pain of having to add a DateTime.MinDate() to all my Start and End Times?
--- update ---
1 - I like to be able to request if Date or StartTime is Null seperartly on the Entry object.
2 - Entry should allow the user to input Duration without indication of date. Even a default date like DateTime.MinDate() seems like a poor design. (This is why i choose TimeSpan not Start and EndTime)
Don't split up the date and time components where you store the data. You can provide properties to extract those if you like:
public class Entry {
public DateTime StartPoint { get; set; }
public TimeSpan Duration { get; set; }
public DateTime StartDate { get { return StartPoint.Date; } }
public TimeSpan StartTime { get { return StartPoint.TimeOfDay; } }
public DateTime EndPoint { get { return StartPoint + Duration; } }
public DateTime EndDate { get { return EndPoint.Date; } }
public TimeSpan EndTime { get { return EndPoint.TimeOfDay; } }
}
Update:
If you want to have null values for date and time, you can add properties for that without having to split the date and time:
public class Entry{
private DateTime _startPoint;
public bool HasStartDate { get; private set; }
public bool HasStartTime { get; private set; }
public TimeSpan Duration { get; private set; }
private void EnsureStartDate() {
if (!HasStartDate) throw new ApplicationException("Start date is null.");
}
private void EnsureStartTime() {
if (!HasStartTime) throw new ApplicationException("Start time is null.");
}
public DateTime StartPoint { get {
EnsureStartDate();
EnsureStartTime();
return _startPoint;
} }
public DateTime StartDate { get {
EnsureStartDate();
return _startPoint.Date;
} }
public TimeSpan StartTime { get {
EnsureStartTime();
return _startPoint.TimeOfDay;
} }
public DateTime EndPoint { get { return StartPoint + Duration; } }
public DateTime EndDate { get { return EndPoint.Date; } }
public TimeSpan EndTime { get { return EndPoint.TimeOfDay; } }
public Entry(DateTime startPoint, TimeSpan duration)
: this (startPoint, true, true, duration) {}
public Entry(TimeSpan duration)
: this(DateTime.MinValue, false, false, duration) {}
public Entry(DateTime startPoint, bool hasStartDate, bool hasStartTime, TimeSpan duration) {
_startPoint = startPoint;
HasStartDate = hasStartDate;
HasStartTime = hasStartTime;
Duration = duration;
}
}
You could use a TimeSpan for your StartTime and EndTime properties. That's what the DateTime.TimeOfDay property returns.
There's also the DateTime.Date property which returns a DateTime with the time element set to midnight.
Having said that, I would probably recommend ditching your Date property altogether and storing full DateTimes (ie, date and time) in your StartTime and EndTime properties.
You're far better off leaving your references as DateTimes. If you only store the time, then you have issues when your Entry spans more than a 24-hour period. Store them as DateTimes as you have them now, and apply whatever formatting is necessary to represent just the time portion to your end-user.
I'd like to compliment Guffa's answer with two more best practices:
Store all of your dates as UTC in the database.
Avoid System.DateTime and favor System.DateTimeOffset.
SQL Server 2008’s datetimeoffset type is the equivalent of DateTimeOffset.

Categories

Resources