How do I create a Descriptive DateConverter in C#? - c#

I have a DateConverter class that does all the basics. However, I want to add another type to it. I want to be able to have a 'Descriptive' type that returns the difference between the date and DateTime.Now formatted as a string.
IE: "seconds ago", "7 minutes ago", "8 hours ago"
Whichever the larger increment is.
I suppose the only thing I am missing is figuring out how to get the difference between the two dates in seconds. C# is still a little new to me.

you can subtract two datetime objects and it will return TimeSpan
and you can get Seconds property of TimeSpan
var timespan = (datetime1 - datetime2);
var seconds = timespan.Seconds;
var Minutes = timespan.Minutes;
var hours = timespan.Hours;
I suppose the only thing I am missing is figuring out how to get the
difference between the two dates in seconds.
then you want timespan.TotalSeconds

what about using an extension method instead, like
public static string FromNowFormatted(this DateTime date)
{
var sb = new StringBuilder();
var t = DateTime.Now - date;
var dic = new Dictionary<string, int>
{
{"years", (int)(t.Days / 365)},
{"months", (int)(t.Days / 12)},
{"days", t.Days},
{"hours", t.Hours},
{"minutes", t.Minutes},
{"seconds", t.Seconds},
};
bool b = false;
foreach (var e in dic)
{
if (e.Value > 0 || b)
{
var v = e.Value;
var k = v == 1 ? e.Key.TrimEnd('s') : e.Key ;
sb.Append(v + " " + k + "\n");
b = true;
}
}
return sb.ToString();
}
demo
Note: there are some things with this code you'll need to fix-up such as the ways years and months are calculated.
Edit: you could use Noda Time's Period.Between() which calculates the difference and then just have an extension method as above, that would simply format it in a similar way. see the secion "Finding a period between two values" here for more info.

Related

How can I use a Lambda Expression to calculate a function in C# with variables from multiple nodes in a LinkedList<object>?i

static void Main(string[] args)
{
LinkedList<Day> Days = new LinkedList<Day>();
Day day1 = new Day() { sunUp = 800, sunDown = 1800 };
Day day2 = new Day() { sunUp = 755, sunDown = 1805 };
Day day3 = new Day() { sunUp = 750, sunDown = 1810 };
Day day4 = new Day() { sunUp = 745, sunDown = 1815 };
Day day5 = new Day() { sunUp = 740, sunDown = 1820 };
Days.AddLast(day1);
Days.AddLast(day2);
Days.AddLast(day3);
Days.AddLast(day4);
Days.AddLast(day5);
Console.WriteLine("There is an average of {0} minutes of night over the past {1} days.", Calculator(Days), Days.Count);
}
public static int ToMinutes(int time)
{
return time / 100 * 60 + time - time / 100 * 100;
}
class Day
{
public int sunUp;
public int sunDown;
}
public static int Calculator(LinkedList<Day> Days)
{
var current = Days.First;
int nightDuration = 0;
int count = 0;
while (current.Next != null)
{
nightDuration += ToMinutes(current.Value.sunDown) - ToMinutes(current.Next.Value.sunUp);
current = current.Next;
count++;
}
return nightDuration/count;
}
Requirement: Day must be stored in a LinkedList.
Is there a clean Lambda Expression equivalent to the Calculator method above? I am having trouble using Lambda's to calculate a function with variables across connected nodes.
Thanks for the help!
Cheers,
Kyle
Quite a few things are wrong with your pseudo code of using a calculator (for addition in this instance)
You have a class that has two integers. I believe your idea is to add the two numbers together along with two numbers from every other node of the linked list. But, you are adding only the first number from Pair and adding it to 2nd number of the next node... dont think it will work.
array = current.value.int1 + current.Next.value.int2;
should be
array = current.value.int1 + current.value.int2;
Working Example
public static int Calculator(LinkedList<Pair> Pairs)
{
var current = Pairs.First;
int sum = 0;
while (current != null)
{
sum += current.Value.num1 + current.Value.num2;
current = current.Next;
}
return sum;
}
Ran this in the main,
LinkedList<Pair> Pairs = new LinkedList<Pair>();
Pair pair = new Pair() { num1 = 1, num2 = 5 };
Pairs.AddFirst(pair);
Pairs.AddLast(pair);
Pairs.AddLast(pair);
Console.WriteLine( Calculator(Pairs));
Output
18
Lambda expression you can use to add up all the linked lists can be summed up as,
Console.WriteLine(Pairs.Sum(x => x.num1 + x.num2));
Output
18
Hope it helps
Looking at your example i see what you're trying to say. So you want to the same logic as in your Calculator method but using Lambda expression. The most easy way to do this is to use Language Integrated Query (LINQ).
So breaking up your Calculator() method we see it actually contains 2 elements.
Sum up each value after you've combined num1 with num2 in the List
Divide the end result with the total count in the list.
The most easy way to do this is by using Enumerable.Sum and then devide that value by the count of the list. For example:
Pairs.Sum(pair => pair.num1 + pair.num2) / Pairs.Count
Result: 6
Here a code example
Enumerable.Sum is extension method from System.Linq namespace. It returns sum of numeric values in collection.
Although Lambda expressions are awesome, please do bare in mind that readability is also very important. They are basically anonymous functions which, if the logic gets complex, will be hard to understand for other developers/team members.
Hope this helps. Good luck!
EDIT
So if i understand it correctly you're having a hard time using lambda expressions with nested methods. Like for example your Calculator() method makes use of another method ToMinutes.
Using the code from my first example, we can still use the logic. The most important part we have to change is the logic within the Sum. E.g.
Days.Sum(day => ToMinutes(day.sunDown) - ToMinutes(day.sunUp )) / totalDays;
This is just one of the ways to do it. Another option is to create an ExtensionMethod. E.g.
namespace ExtensionMethods
{
public static class IntExtensions
{
public static int ToMinutes(this int time)
{
return time / 100 * 60 + time - time / 100 * 100;
}
}
}
Above the class you want to use the extension method just include it by writing
using ExtensionMethods;
at the top of your document.
This will give another option to all your integers to parse the value to a different value. In our case, to minutes.
int totalDays = Days.Count;
int averageMinutes = Days.Sum(day => day.sunDown.ToMinutes() - day.sunUp.ToMinutes()) / totalDays;
Console.WriteLine("There is an average of {0} minutes of night over the past {1} days.", averageMinutes, totalDays);
But again. This is one of the ways of doing it. I would recommend you to get and read C# in Depth by Jon Skeet and also dive into the clean code principles.
Anyways, i think i have made myself clear. See example code for more details.
Good luck!

Convert Decimal (62.25) to Hours and Minutes String (62 Hours and 15 Minutes)?

Is there a simple way to convert my Decimal value (ex. 62.25) to a returned string like "62 Hours : 15 Minutes"? I have been looking at several posts similar to this one (Decimal Hour into Time?), but even the suggestions that say they work for figures greater than 24hr periods are not working.
This is pretty simple:
var decimal = 62.25;
var hours = Math.floor(decimal);
var mins = 60 * (decimal - hours);
console.log(`${hours} hours, ${mins} minutes`);
In c#, you can likely just leverage the TimeSpan class. See .NET fiddle here:
using System;
public class Program
{
public static void Main()
{
var dec = 62.25m;
var ts = TimeSpan.FromHours((double)dec);
Console.WriteLine("{0} Hours, {1} Minutes", Math.Floor(ts.TotalHours), ts.Minutes);
}
}
This question is really simple and this answer too and has a lot of basic programming on it and there are a lot of different ways of doing it. The simple one is to use cast:
double date = getDate(); //62.25. I don't know where you get date from :P
int hours = (int)date; //62
float minutes = 60* (date - hours); //60 * (62.25 - 62 ) = 15
Write( "Hours: " + hours + "\nMinutes: " + minutes );
Consider doing valdiations if minutes have decimal points (12.3 minutes should be rounded, etc.)
Give this one a try
void Main()
{
Console.WriteLine (DecimalToTime(62.25)); // will output 62 Hours:15
}
string DecimalToTime(double dbTime)
{
int hour = (int)dbTime;
int minute = (int)((dbTime - hour) * 60);
string time = hour + " Hours:" + minute;
return time;
}

DateTime.Compare doesn't work as expected

I'm trying to compare two DateTime objects ignoring the seconds with the below function, but it gives the wrong result even though both of the DateTime objects are have the same values. Can't figure out how to exactly get it working, any help would be highly appreciated.
public static int CompareDateTime(DateTime d1, DateTime d2)
{
d1 = d1.AddSeconds(-1 * d1.Second);
d2 = d2.AddSeconds(-1 * d2.Second);
int result = DateTime.Compare(d1, d2);
string relationship;
if (result < 0)
relationship = "is earlier than";
else if (result == 0)
relationship = "is the same time as";
else
relationship = "is later than";
Console.WriteLine("{0} {1} {2}", d1, relationship, d2);
return result;
}
Result:
3/7/2017 2:54:00 PM is later than 3/7/2017 2:54:00 PM
The problem here is that you aren't truncating as you expect to. The best way to truncate is to create an entirely new DateTime object from the constructor by doing the following:
d1 = new DateTime(d1.Year, d1.Month, d1.Day, d1.Hour, d1.Minute, 0);
d2 = new DateTime(d2.Year, d2.Month, d2.Day, d2.Hour, d2.Minute, 0);
This will ensure you are only comparing the data you want.
In this specific case it is the milliseconds that may have been part of the datetime that were being left by your attempted truncation.
You just need to ignore the milliseconds as well. You could add this to your code, or go for the more 'elegant' solution suggested by #Chris
d1 = d1.AddMilliseconds(-1 * d1.Millisecond);
d2 = d2.AddMilliseconds(-1 * d2.Millisecond);
Everyone is giving a solution for truncating time... So I'll give the one that I normally use... A DateTime is the number of ticks starting from 0:00:00 on January 1, 0001... so if you know how many ticks there are in a minute (there is a readonly field for that: TimeSpan.TicksPerMinute) you can count the number of minutes from January 1, 0001...
long minutes1 = d1.Ticks / TimeSpan.TicksPerMinute;
long minutes2 = d2.Ticks / TimeSpan.TicksPerMinute;
int result = minutes1.CompareTo(minutes2);
(note that there are various "useful" values in TimeSpan: TicksPerDay, TicksPerHour, TicksPerMinute, TicksPerSecond, TicksPerMillisecond)

How can I segregate properties of DateTime?

I would like to write: if the result of the difference of 2 DateTimes is longer than 3 hours then.... stuff in the if statement happens. But I only need properties in seconds or minutes, can I extract just that from the DateTime object?
if(diffResult > DateTime.Hour(3))
{
}
I also want to know if its possible to divide DateTime by periods. Say I want to split my diffResult (which is the difference between 2 DateTimes) into 3 periods or perhaps for every 3 seconds my counter gets one added to it.
For the first part:
You can subtract two DateTimes to get a TimeSpan there you can get the total of various units - for example:
if ( (secondTime - firstTime).TotalMinutes > 180.0) ...
or you could use TimeSpan directly:
if (secondTime - firstTime > TimeSpan.FromHours(3)) ...
for the secondpart you have to do some calculation yourself:
var diff = secondTime - firstTime;
var period = TimeSpan.FromSeconds(diff.TotalSeconds / 3.0);
for (var time = firstTime; time < secondTime; time += period)
{ /* do your stuff */ }
U can compare using the follow code:
DateTime dt = new DateTime();
dt = DateTime.Now;
dt.AddHours(3);
int h = (int)DateTime.Now.Hour;
if (dt.Hour == h )
//Do something
else
//do otherthing
You can do this:
TimeSpan time = new TimeSpan(3, 0, 0);
if (date1.Subtract(date2) > time)
{
//YourCode
}
For the second, this article should be useful:
http://www.blackwasp.co.uk/TimespanMultiplication.aspx
The methods your asking about return integer results. What exactly is your question? DateTime.Hour(3) would not even compile.
I think you are looking for DateTime.Now.AddHours(3.0)
I should be clear, the only reason this answer is this sparse, is because of the invalid code in the author's question which. Since I don't attempt to guess at what people actually want, its up to the author, to clarify what he wants exactly.
All he has to do is subtract two DateTime values and compare it to a TimeSpan

How can I get correct payperiod from date?

I feel like this is math problem more than anything. My company has employees all over the country. Some parts of the company are on an "odd" pay cycle and some are on "even". I call the starting date of a given pay period a "payperiod". I need to do two things:
1) determine the payperiod in which a given date falls
//Something like this:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{ .. }
2) get a list of payperiods between two dates:
//Something like this:
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{ .. }
I'm using a couple dates as fixed standards on which to base any future pay period dates. The fixed standard dates for even and odd are as follows:
Even - 01/04/09
Odd - 01/11/09
Each pay period starts on the sunday of the week and goes for two weeks. For instance, using the standard dates above, the first even pay period starts on 01/04/09 and ends on 01/17/09. The first odd pay period starts on 01/11/09 and ends on 01/24/09. As you can see, there is some overlap. We have thousands of employees so it's necessary to split them up a bit.
I have a solution that is based on week numbers but it's clunky and has to be "fixed" every new year. I'm wondering how you would handle this.
Not fully optimized or tested, but this is what I came up with:
const int DaysInPeriod = 14;
static IEnumerable<DateTime> GetPayPeriodsInRange(DateTime start, DateTime end, bool isOdd)
{
var epoch = isOdd ? new DateTime(2009, 11, 1) : new DateTime(2009, 4, 1);
var periodsTilStart = Math.Floor(((start - epoch).TotalDays) / DaysInPeriod);
var next = epoch.AddDays(periodsTilStart * DaysInPeriod);
if (next < start) next = next.AddDays(DaysInPeriod);
while (next <= end)
{
yield return next;
next = next.AddDays(DaysInPeriod);
}
yield break;
}
static DateTime GetPayPeriodStartDate(DateTime givenDate, bool isOdd)
{
var candidatePeriods = GetPayPeriodsInRange(givenDate.AddDays(-DaysInPeriod), givenDate.AddDays(DaysInPeriod), isOdd);
var period = from p in candidatePeriods where (p <= givenDate) && (givenDate < p.AddDays(DaysInPeriod)) select p;
return period.First();
}
I haven't tested for many test cases, but I think this fits the bill:
public static DateTime getPayPeriodStartDate(DateTime givenDate, string EvenOrOdd)
{
DateTime newYearsDay = new DateTime(DateTime.Today.Year, 1, 1);
DateTime firstEvenMonday = newYearsDay.AddDays((8 - (int)newYearsDay.DayOfWeek) % 7);
DateTime firstOddMonday = firstEvenMonday.AddDays(7);
TimeSpan span = givenDate - (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday);
int numberOfPayPeriodsPast = span.Days / 14;
return (EvenOrOdd.Equals("Even") ? firstEvenMonday : firstOddMonday).AddDays(14 * numberOfPayPeriodsPast);
}
public static List<DateTime> getPayPeriodsBetween(DateTime start, DateTime end, string EvenOrOdd)
{
DateTime currentPayPeriod = getPayPeriodStartDate(start, EvenOrOdd);
if (currentPayPeriod < start) currentPayPeriod = currentPayPeriod.AddDays(14);
List<DateTime> dtList = new List<DateTime>();
while (currentPayPeriod <= end)
{
dtList.Add(currentPayPeriod);
currentPayPeriod = currentPayPeriod.AddDays(14);
}
return dtList;
}
I am sure it can be improved.
I had a need to do something similar and was able to do it very easily using LINQ. Simply build up a List for even and odd and then query between dates from the odd/even as necessary. Also, I recommend you move to an emum for parameters like EvenOrOdd where you have fixed values.
I had a similar problem a few months ago, and I ended up writing a quick script to create entries in a database for each pay period so I never had to actually do the math. This way, The system works the same speed, and doesn't have to do any slow iterations every time a period is requested.
That being said, you can always take the starting date, and add two weeks (or however long your periods are) over and over until you reach the dates you specify in the function call. This is a bit ugly, and the longer it sits in production, the slower it gets (since the dates are getting further and further apart).
Both ways are trivial to implement, it's just a matter of what kind of resources you have at hand to tackle the issue.
So, for number 1: Start with either 1/4/2009 or 1/11/2009 (depending on even/odd pay week) and add 2 weeks until the givenDate is less than the date you're testing + 2 weeks. That's the start of the period.
For number 2: Same thing, start at the date and add 2 weeks until you're within the date range. While you're there, add each item to a list. As soon as you're past the last date, break out of your loop and return your shiny new list.
If you used my method and went with a database to house all this info, it turns into 2 simple queries:
1)SELECT * FROM payperiods WHERE startdate<=givenDate ORDER BY startdate LIMIT 1
2) SELECT * FROM payperiods WHERE startdate>=givenDate AND enddate<=givenDate ORDER BY startdate
It works perfectly. I have tested.
public static DateTime GetFirstDayOfWeek(DateTime dayInWeek)
{
CultureInfo _culture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
CultureInfo _uiculture = (CultureInfo)CultureInfo.CurrentUICulture.Clone();
_culture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
_uiculture.DateTimeFormat.FirstDayOfWeek = DayOfWeek.Monday;
System.Threading.Thread.CurrentThread.CurrentCulture = _culture;
System.Threading.Thread.CurrentThread.CurrentUICulture = _uiculture;
// CultureInfo defaultCultureInfo = CultureInfo.CurrentCulture;
DayOfWeek firstDay = _culture.DateTimeFormat.FirstDayOfWeek;
DateTime firstDayInWeek = dayInWeek.Date;
// Logic Of getting pay period Monday(Odd monday)
int i = Convert.ToInt32(firstDay);
while (firstDayInWeek.DayOfWeek != firstDay)
if (i % 2 != 0)
{ firstDayInWeek = firstDayInWeek.AddDays(-1); }
else
{
firstDayInWeek = firstDayInWeek.AddDays(-2);
}
return firstDayInWeek;
}

Categories