Determining the date logic based on a given string in c# - c#

I have written a logic in c# that determines the nextCallDate based on the given cobDate. cobDate is current date -1.
So if there are more than one future date in the given string then it should return the nearest future date to the cobdate and ignore the rest
For eg if the cobdate is 2020/02/12 and the string is
;2;4;2;5;20180328;3;103.3750;5;20190328;3;102.250;5; 20200328;3;101.1250;5;20210328;3;100.00;
Then NextCallDate would be 2020/03/28.
I need to return blank for dates in the past.
So say in the example if the given string has all the dates in the past then it should return blank.
Given string ;2;1;2;5;20120918;3;100.000000;
Here is what I have written
private DateTime? GetNextCallDate(string nextCallDate)
{
DateTime cobDate = DateTime.Now.Date.AddDays(-1);
var parts = nextCallDate.Split(';');
foreach (var part in parts)
{
DateTime parsedNextCallDate = DateTime.Parse(part);
if (parsedNextCallDate.Date > cobDate.Date)
{
return parsedNextCallDate;
}
}
return null;
}

You should probably be using DateTime.TryParse instead of Parse, since some of the values are not dates. Also, it looks like you're returning the first date that's greater than cobDate, not the nearest one.
To resolve this, we first set parsedNextCallDate to null, and this will be our default return value. Then we can check each part if it's a DateTime using the return value from TryParse, and then compare the value to both cobDate and parsedNextCallDate. If the date is greater than cobDate and less than parasedNextCallDate (or if parasedNextCallDate isn't set yet), then we update parasedNextCallDate to the new value. At the end, we just return parasedNextCallDate:
public static DateTime? GetNextCallDate(string input)
{
DateTime? nextCallDate = null;
if (string.IsNullOrWhiteSpace(input)) return nextCallDate;
var yesterday = DateTime.Today.AddDays(-1);
var inputItems = input.Split(';');
foreach (var inputItem in inputItems)
{
DateTime itemDate;
// If inputItem is a DateTime and it's greater than yesterday
if (DateTime.TryParseExact(inputItem.Trim(), "yyyyMMdd", null,
DateTimeStyles.None, out itemDate) &&
itemDate.Date > yesterday)
{
// and if nextCallDate doesn't have a value or the parsed value
// is less than nextCallDate, assign nextCallDate to this value
if (!nextCallDate.HasValue || itemDate < nextCallDate)
{
nextCallDate = itemDate;
}
}
}
return nextCallDate;
}

Here's one way solve your problem. Breaking things down into steps often makes things easier to reason with and easier to test. I'm often working on server side apps so I like the new span/memory classes. So first thing is to split our input string into chunks:
static IEnumerable<ReadOnlyMemory<char>> ReduceToPossibleDates(string source)
{
const int ExpectedDateLen = 9; // includes separator
int last = 0;
var mem = source.AsMemory();
for (int i = 0; i < source.Length; ++i)
{
if (';' == mem.Span[i])
{
int length = i - last;
if (length == ExpectedDateLen)
{
yield return mem.Slice(last+1,length-1);
}
last = i;
}
}
}
This gives us a stream of ReadOnlyMemory that all contains what we think should be dates. Next we can do another method to consume those chunks and turn them into dates.
static IEnumerable<DateTime> ToDateTime(IEnumerable<ReadOnlyMemory<char>> rawDates)
{
foreach (var rawDate in rawDates)
{
if (DateTime.TryParseExact(rawDate.Span,"yyyyMMdd".AsSpan(),
CultureInfo.InvariantCulture,
DateTimeStyles.None, out var date))
yield return date;
}
}
Once we have that we can treat the stream of dates however we want. In this case we check to find the first that's after our COB.
static void Main(string[] _)
{
const string GoodData = ";2;4;2;5;20180328;3;103.3750;5;20190328;3;102.250;"+
"5;20200328;3;101.1250;5;20210328;3;100.00;";
const string NoDateData = ";2;1;2;5;20120918;3;100.000000;";
var cobDate = new DateTime(2020, 2,12); // some actual close of business date...
var nextCallDate = ToDateTime(ReduceToPossibleDates(GoodData))
.FirstOrDefault(x => x >= cobDate);
var noDateExpected = ToDateTime(ReduceToPossibleDates(NoDateData))
.FirstOrDefault(x => x >= cobDate);
if (nextCallDate != default(DateTime))
Console.WriteLine(nextCallDate);
else
Console.WriteLine("no call date.");
if (noDateExpected != default(DateTime))
Console.WriteLine(nextCallDate);
else
Console.WriteLine("no call date.");
}
It would be a little cleaner with extension methods but you get the idea.

Related

Remove string part in a variable containing date in c#

I have a string variable that holds the value of "02/04/2018 to 08/04/2018".
string dateRange = "02/04/2018 to 08/04/2018";
I have a function in c# that gets all the date within the range of 02/04/2018 to 08/04/2018 as per below.
public string getDateRange(string dateRange) {
var selectedDates = new List<DateTime?>();
for (var date = Convert.ToDateTime("02/04/2018");
date <= Convert.ToDateTime("08/04/2018");
date = date.AddDays(1)) {
selectedDates.Add(date);
}
foreach (var date in selectedDates) {
Console.WriteLine(date);
}
return selectedDates;
}
What I want to achieve in this method is to remove the word to in the date range and pass the starting date and ending date separately. Can someone please help ?
You can use String.Split() to separate the dates:
public string[] separateDates(string dateRange)
{
string[] dateSplit = dateRange.Split(new string[] { "to" }, StringSplitOptions.RemoveEmptyEntries);
return new string[]{dateSplit[0].Trim(), dateSplit[1].Trim()};
}
The method returns a string array that holds the first ("02/04/2018") and the second date ("08/04/2018"):
static void Main()
{
string dateRange = "02/04/2018 to 08/04/2018";
string[] myDates = separateDates(dateRange);
string firstDate = myDates[0];//"02/04/2018"
string secondDate = myDates[1];//"08/04/2018"
}
EDIT:
I have implemented my method that separates the dates into your method:
public List<DateTime?> getDateRange(string dateRange)
{
var selectedDates = new List<DateTime?>();
string[] dateSplit = dateRange.Split(new string[] { "to" }, StringSplitOptions.RemoveEmptyEntries);
for (var date = Convert.ToDateTime(dateSplit[0].Trim());
date <= Convert.ToDateTime(dateSplit[1].Trim());
date = date.AddDays(1))
{
selectedDates.Add(date);
}
foreach (var date in selectedDates)
{
Console.WriteLine(date.Value.ToString("dd/MM/yyyy", CultureInfo.InvariantCulture));
}
return selectedDates;
}
The method now returns List<DateTime?> instead of string because the type of selectedDates is List<DateTime?>. I also made a modification to the console output, now the dates are printing to the console in the following format dd/MM/yyyy (e.g. 02/04/2018).
LastIndexOf: This method searches strings from the right. It finds the location of the last occurrence of a letter or substring. It is the reversed version of IndexOf.
public static void Main(string[] args)
{
string str = "02/04/2018 to 08/04/2018";
int pos = str.LastIndexOf("to");
string result = str.Substring(0, pos) + " " + str.Substring(pos + 2);
Console.WriteLine(result);
}
Fiddle
Its printing the date as : 02.04.2018 00:00:00. I have specified the string to dd/MM/yyyy. It just removes the 00:00:00 but keeps the dot in between.
Here is the little amendment to fix that:
Console.WriteLine(date.Value.ToString("dd/MM/yyyy"));

Compare 2 strings of time in C#

So I've got a string 00:00:15:185 which I need to tell is greater than 15 seconds.
Time format is HH:m:ss:FFF
This is clearly longer than 15 seconds but I can't compare it properly.
Current code is this:
value = "00:00:15:185";
if (DateTime.Parse(value) > DateTime.Parse("00:00:15:000"){
//do stuff
}
It's giving exceptions when I run it all the time and the program doesn't work when it should
Your string doesn't represent a time, but an amount of time. We have TimeSpan for that.
var value = "00:00:15:185";
if (TimeSpan.ParseExact(value, #"hh\:mm\:ss\:FFF", CultureInfo.InvariantCulture)
> TimeSpan.FromSeconds(15))
{
//do stuff
}
Another option(apart from #rob 's answer), use DateTime.ParseExact
var value = "00:00:15:185";
if (DateTime.ParseExact(value, "HH:mm:ss:fff", CultureInfo.InvariantCulture) >
DateTime.ParseExact("00:00:15:000", "HH:mm:ss:fff", CultureInfo.InvariantCulture))
{
// logic here.
}
DateTime time = DateTime.Now;
String result = time.ToString("HH:mm ");
DateTime firstTimr = DateTime.ParseExact(reader["shift_start_time"].ToString(), "HH:mm:tt", null);
String firstTimr1 = firstTimr.ToString("HH:mm ");
DateTime lastTime = DateTime.ParseExact(reader["Shift_last_time"].ToString(), "HH:mm:tt", null);
String lastTime1 = lastTime.ToString("HH:mm ");
if (DateTime.Parse(result) >= DateTime.Parse(firstTimr1) && (DateTime.Parse(result) <= DateTime.Parse(lastTime1)))
{
`enter code here` MessageBox.Show("First Shit");
}

Parse value with Currency symbol

I have looked to multiple SO questions on parsing currency, the best (recommended) way seems to be the one I'm trying below:
var payout = decimal.Parse("$2.10", NumberStyles.Currency | NumberStyles.AllowDecimalPoint);
However, it throws and exception: Input string is not in the correct format.
I don't know what I'm doing wrong?
EDIT
Thanks for the answers. Additional info: the hard-coded currency value I gave was just an example. I have a list of currencies:
€2,66
$2.10
$5.55
etc.
I cannot determine the culture info in advance. Any ideas?
Similar approach #un-lucky mentioned as one of the answer, I tried making it generic and work for every Symbol/Format
public static decimal ParseCurrencyWithSymbol(string input)
{
var cultures = CultureInfo.GetCultures(CultureTypes.AllCultures)
.GroupBy(c=> c.NumberFormat.CurrencySymbol)
.ToDictionary(c=> c.Key, c=>c.First());
var culture = cultures.FirstOrDefault(c=>input.Contains(c.Key));
decimal result = 0;
if(!culture.Equals(default(KeyValuePair<string,CultureInfo>)))
{
result = decimal.Parse(input, NumberStyles.Currency | NumberStyles.AllowDecimalPoint, culture.Value);
}
else
{
if( !decimal.TryParse(input, out result))
{
throw new Exception("Invalid number format");
}
}
return result;
}
Usage
decimal output = ParseCurrencyWithSymbol("$2.10");
Working Code
You can try like this:
decimal currencyValue;
string inputCurrency = "$12.6";
if (decimal.TryParse(inputCurrency, NumberStyles.Currency, CultureInfo.CreateSpecificCulture("en-US"), out currencyValue))
{
// proceed with currencyValue
}
else
{
//Show error ; Conversion failed
}
For dealing with all currencies you can use the following:
Dictionary<char, string> currencyCulture = new Dictionary<char, string>();
currencyCulture.Add('$', "en-US");
currencyCulture.Add('€', "en-IE");
// populate all posible values here
decimal currencyValue;
string inputCurrency = "€2,66";
char currencySymbol= inputCurrency.ToCharArray()[0];
CultureInfo currentCulture= CultureInfo.CreateSpecificCulture(currencyCulture[currencySymbol]);
if (decimal.TryParse(inputCurrency, NumberStyles.Currency, currentCulture, out currencyValue))
{
// proceed with currencyValue
}
else
{
//Show error ; Conversion failed
}
You can choose culture Names from here
Pertinent to your particular case, you may use the following code snippet:
var payout = decimal.Parse("$2.10".Replace("$",""));
If you don't know what the currency symbol would be, then try the following solution:
string _money = "$2.10";
var payout = decimal.Parse(_money.Substring(1));
Dealing with commas and decimal points is much more difficult: if this is the issue, refer to the solution given by member #un-lucky.
Hope this may help.
How about a CleanCurrency method?
/// Loops each char in the string and returns only numbers, . or ,
static double? CleanCurrency(string currencyStringIn) {
string temp = "";
int n;
for (int i = 0; i < currencyStringIn.Length; i++) {
string c = currencyStringIn.Substring(i, 1);
if (int.TryParse(c, out n) || c == "." || c == ",") {
temp += c;
}
}
if (temp == "") {
return null;
else {
return double.Parse("0" + temp);
}
}
The idea here being to just get an actual number regardless of what the string content is.
double? payout = CleanCurrency("$3.50");
I've expounded on Hari Prasad's answer. With this you can minimize the culture result. Update "SupportedCultures" with the ones you might use in your app.
private static readonly List<string> SupportedCultures = new List<string> {"en-US", "en-GB", "fr-FR"};
public static void Main()
{
var (amount, culture) = ParseCurrencyWithSymbol("$256.12");
Console.WriteLine($"{culture?.Name} | {amount}");
var (amount2, culture2) = ParseCurrencyWithSymbol("£389.17");
Console.WriteLine($"{culture2?.Name} | {amount2}");
var (amount3, culture3) = ParseCurrencyWithSymbol("€421,10");
Console.WriteLine(culture3 != null ? $"{culture3.Name} | {amount3}" : "Failed!");
}
public static Tuple<decimal?, CultureInfo> ParseCurrencyWithSymbol(string input)
{
var culture = CultureInfo.GetCultures(CultureTypes.AllCultures)
.Where(x => SupportedCultures.Contains(x.Name))
.FirstOrDefault(c => input.Contains(c.NumberFormat.CurrencySymbol));
if (culture == null) return new Tuple<decimal?, CultureInfo>(null, null);
return new Tuple<decimal?, CultureInfo>(decimal.Parse(input,
NumberStyles.Currency | NumberStyles.AllowCurrencySymbol |
NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands, culture), culture);
}

How To Display Date In Locale For 0 Values?

Ok, so I have a date stored in UK format (dd/mm/yy) which I need to display in the locale of wherever the user might be.
The issue is that this date can be 000000 (00/00/2000); so I can't convert it to DateTime directly, as DateTime doesn't support 0 values for day or month.
I have this so far:
int dateInt = ddmmyy;
var year = (dateInt % 100) + 2000;
var month = (dateInt / 100) % 100;
var day = (dateInt / 100000);
var result = new DateTime(year, month, day); //2014/00/00 at this point, so breaks.
var resultStr = result.ToString(CultureInfo.InvariantCulture);
return resultStr;
What's the correct way to add support for 0 values initially? I've tried changing the 0 to 1 before converting to DateTime, running the conversion and then replacing with a 0 again; but due to culture variants I see no way that this method can support other cultures, which is the purpose of this conversion to begin with.
Any ideas? I'm guessing this is a common issue.
Is this what you need ?
using System;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
int[] savedDates = new int[] { 000000, 010000, 000013 };
foreach (var item in savedDates)
{
DateTime date = ConvertToDate(item);
Console.WriteLine(item.ToString("D6") + " => " + date.ToShortDateString());
}
Console.ReadLine();
}
private static DateTime ConvertToDate(int item)
{
string temp = item.ToString("D6");
int day = int.Parse(temp.Substring(0, 2));
int month = int.Parse(temp.Substring(2, 2));
int year = int.Parse(temp.Substring(4, 2));
if (day == 0)
day = 1;
if (month == 0)
month = 1;
year += 2000;
return new DateTime(year, month, day);
}
}
}
I would not store dates like this as the methodology for doing so is already provided by the .NET framework.
The best way to store dates would be to use Culture.InvariantCulture for string conversion cases and then convert to local culture for display purposes as necessary. DateTime itself is culture-independent so converting between cultures is very easy.

Unable to parse Oracle timestamp in C#

I have timestamp of Oracle:
string timestamp = "23-JUN-14 09.39.04.000000000 AM";
I am not able to parse it into system date time object. I used:
CultureInfo provider = CultureInfo.InvariantCulture;
String format = "yy-MMM-dd hh:mm:ss:fffffff";
string timestamp = "10-DEC-07 10.32.47.797201123 AM";
{
var date = DateTime.ParseExact(timestamp, format, provider);
DateTime dateTime = DateTime.ParseExact(timestamp.ToString(), "dd-MMM-y HH:mm:ss", CultureInfo.InvariantCulture);
}
It is still passing error. It is working 7 f after m but not more than that. I used try Parse, try ParseExact - is there any way?
According to https://stackoverflow.com/a/23198962/328864, there is no way to skip parts of an exact pattern, so i guess you could do something like this:
CultureInfo provider = CultureInfo.InvariantCulture;
string timestamp = "10-DEC-07 10.32.47.797201123 AM";
String format = String.Format("yy-MMM-dd hh.mm.ss.fffffff{0} tt", timestamp.Substring(26,2));
DateTime date = DateTime.ParseExact(timestamp, format, provider);
Console.WriteLine(date);
Not very pretty though.
Once we started to use ODP.NET, we had to implement an extension like below:
public static T ConvertOracleValue<T>(this object value)
{
if (value != null)
{
Type typeOfValue = value.GetType();
if (typeOfValue.Namespace.Contains("Oracle.DataAccess"))
{
if (typeOfValue.Name.Equals("OracleTimeStamp"))
{
int tempInt = 0;
Oracle.DataAccess.Types.OracleTimeStamp ots = (Oracle.DataAccess.Types.OracleTimeStamp)value;
tempInt = Int32.TryParse(ots.Millisecond.ToString("000").Substring(0, 3), out tempInt) ? tempInt : 0;
DateTime ret = new DateTime(ots.Year, ots.Month, ots.Day, ots.Hour, ots.Minute, ots.Second, tempInt);
return ConvertHelper.ConvertValue<T>(ret);
}
if (typeOfValue.Name.Equals("OracleTimeStampLTZ"))
{
int tempInt = 0;
Oracle.DataAccess.Types.OracleTimeStampLTZ ots = (Oracle.DataAccess.Types.OracleTimeStampLTZ)value;
tempInt = Int32.TryParse(ots.Millisecond.ToString("000").Substring(0, 3), out tempInt) ? tempInt : 0;
DateTime ret = new DateTime(ots.Year, ots.Month, ots.Day, ots.Hour, ots.Minute, ots.Second, tempInt);
return ConvertHelper.ConvertValue<T>(ret);
}
if (typeOfValue.Name.Equals("OracleTimeStampTZ"))
{
int tempInt = 0;
Oracle.DataAccess.Types.OracleTimeStampTZ ots = (Oracle.DataAccess.Types.OracleTimeStampTZ)value;
tempInt = Int32.TryParse(ots.Millisecond.ToString("000").Substring(0, 3), out tempInt) ? tempInt : 0;
DateTime ret = new DateTime(ots.Year, ots.Month, ots.Day, ots.Hour, ots.Minute, ots.Second, tempInt);
return ConvertHelper.ConvertValue<T>(ret);
}
string temp = value.ToString();
return ConvertHelper.ConvertValue<T>(temp);
}
}
else
{
return default(T);
}
return ConvertHelper.ConvertValue<T>(value);
}
where ConvertHelper.ConvertValue is another extension:
public static class ConvertHelper
{
public static T ConvertValue<T>(object value)
{
Type typeOfT = typeof(T);
if (typeOfT.BaseType != null && typeOfT.BaseType.ToString() == "System.Enum")
{
return (T)Enum.Parse(typeOfT, Convert.ToString(value));
}
if ((value == null || value == Convert.DBNull) && (typeOfT.IsValueType))
{
return default(T);
}
if (value is IConvertible)
{
return (T)Convert.ChangeType(value, typeOfT, new CultureInfo("en-GB"));
}
return (T)Convert.ChangeType(value, typeOfT);
}
}
This worked like a charm in our test, integration and production environments.
.NET DateTime structure has a precision of tick - 100 nanoseconds - 0.0000001 of second - 7 decimal positions after the point.
Oracle TimeStamp has a precision of up to nanosecond - 0.000000001 - 9 decimal positions after the point.
That is why standard DateTime cannot store all possible oracle TimeStamps. And its parsing function simply fail on more precise string representations of TimeStamp.
So, what could be tried:
Format your TimeStamps in query to some format parseable by DataTime(with loss of precision if necessary) - http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements004.htm
Or create your own more precise CustomTimeStamp .Net structure and parse to it manually

Categories

Resources