using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace Vrh.OneReport.Lib.Areas.OneReport.Helpers
{
///
/// Parse a date/time string.
///
/// Can handle time expressions like:
/// - "yesterday": Yesterday
/// - "today+12D": Today twelve days later
/// - "today": This day at midnight.
/// - "now": Right now (date and time).
/// - "2010-12-31"
///
/// In the expression the folowing words can be used: now, today, tommorrow, yesterday.
/// The units are:
/// - Y = year
/// - M = month
/// - D = day
/// - h = hour
/// - m = minute
/// - s = second
///
public class RelativeDateParser
{
private const string ValidUnits = "Y|M|D|h|m|s";
private const string ValidItems = "now|today|tomorrow|yesterday|DAYSBACK";
private static readonly Regex _completeRelativeRegex = new Regex(@"^(" + ValidItems + @")?(\s*(\+|-)?(\d)*(" + ValidUnits + "))*$");
///
/// Parses the given text and converts it to a DateTime if possible.
///
/// The text to be parsed.
/// Datetime
public static DateTime Parse(string input, string dateFormat, string timeFormat)
{
DateTime? result = TryParseCompleteRelativeDateTime(input.Trim());
if (result.HasValue)
{
return result.Value;
}
// Try parse fixed dates like "01/01/2000".
DateTime dt;
List formats = new List();
if (dateFormat != null)
{
dateFormat = dateFormat.Replace("\\/", "/").Replace("/", "\\/");
formats.Add(dateFormat);
if (timeFormat != null)
{
formats.Add(dateFormat + " " + timeFormat);
formats.Add(dateFormat + "T" + timeFormat);
}
}
if (timeFormat != null)
{
formats.Add("T" + timeFormat);
formats.Add(timeFormat);
}
if (formats.Count == 0 || !DateTime.TryParseExact(input, formats.ToArray(), null, System.Globalization.DateTimeStyles.NoCurrentDateDefault | System.Globalization.DateTimeStyles.AssumeLocal, out dt))
{
dt = DateTime.Parse(input);
}
return dt;
}
///
/// Parses the given text, chacks if it is an expression and converts it to a DateTime if possible.
///
/// The text to be parsed.
/// Datetime or null if the text is not an expression.
private static DateTime? TryParseCompleteRelativeDateTime(string input)
{
var match = _completeRelativeRegex.Match(input);
if (!match.Success)
return null;
DateTime dt = DateTime.Now;
if (match.Groups[1].Success)
{
switch (match.Groups[1].Captures[0].Value)
{
case "now":
dt = DateTime.Now;
break;
case "today":
dt = DateTime.Today;
break;
case "yesterday":
dt = DateTime.Today.AddDays(-1);
break;
case "tomorrow":
dt = DateTime.Today.AddDays(1);
break;
}
}
var items = match.Groups[2].Captures;
var signs = match.Groups[3].Captures;
var values = match.Groups[4].Captures;
var units = match.Groups[5].Captures;
//Assumes that each item has a unit member.
Debug.Assert(items.Count == units.Count);
int unitidx, signidx, valueidx;
int value;
for (unitidx = signidx = valueidx = 0; unitidx < units.Count; ++unitidx)
{
var unit = units[unitidx].Value;
var ui = units[unitidx].Index;
if (values[valueidx].Index < ui) //OK, we have a value;
{
value = Convert.ToInt32(values[valueidx++].Value);
}
else
{
value = 1;
}
if (signs[signidx].Index < ui) //OK, we have a sign;
{
value *= (signs[signidx++].Value == "-" ? -1 : 1);
}
dt = AddOffset(unit, value, dt);
}
return dt;
}
///
/// Add/Remove years/days/hours... to a datetime.
///
/// Must be one of ValidUnits
/// Value in given unit to add to the datetime
/// Relative datetime
/// Relative datetime
private static DateTime AddOffset(string unit, int value, DateTime dateTime)
{
switch (unit)
{
case "Y":
return dateTime.AddYears(value);
case "M":
return dateTime.AddMonths(value);
case "D":
return dateTime.AddDays(value);
case "h":
return dateTime.AddHours(value);
case "m":
return dateTime.AddMinutes(value);
case "s":
return dateTime.AddSeconds(value);
default:
throw new Exception("Internal error: Unhandled relative date/time case.");
}
}
}
}