我实际上是在尝试读取xml文件。其中一个值具有后缀,例如“30d
”。这意味着'30天'。所以我试图将其转换为DateTime.Now.AddDays(30)
。要在XML中读取此字段,我决定使用枚举:
enum DurationType { Min = "m", Hours = "h", Days = "d" }
现在我并不确定如何有效地处理这个问题(当涉及枚举时我有点愚蠢)。我应该首先将字符串中的后缀(在本例中为“d”)分开,然后使用enum
语句尝试在switch
中匹配它吗?
我想如果你对我的问题愚蠢,那就是:从30d
到DateTime.Now.AddDays(30)
的最佳方式是什么?
答案 0 :(得分:5)
这似乎是使用正则表达式的好地方;特别是捕获组。
以下是一个工作示例:
using System;
using System.Text.RegularExpressions;
namespace RegexCaptureGroups
{
class Program
{
// Below is a breakdown of this regular expression:
// First, one or more digits followed by "d" or "D" to represent days.
// Second, one or more digits followed by "h" or "H" to represent hours.
// Third, one or more digits followed by "m" or "M" to represent minutes.
// Each component can be separated by any number of spaces, or none.
private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d+)d)?\s*((?<Hours>\d+)h)?\s*((?<Minutes>\d+)m)?", RegexOptions.IgnoreCase);
public static TimeSpan ParseDuration(string input)
{
var match = DurationRegex.Match(input);
var days = match.Groups["Days"].Value;
var hours = match.Groups["Hours"].Value;
var minutes = match.Groups["Minutes"].Value;
int daysAsInt32, hoursAsInt32, minutesAsInt32;
if (!int.TryParse(days, out daysAsInt32))
daysAsInt32 = 0;
if (!int.TryParse(hours, out hoursAsInt32))
hoursAsInt32 = 0;
if (!int.TryParse(minutes, out minutesAsInt32))
minutesAsInt32 = 0;
return new TimeSpan(daysAsInt32, hoursAsInt32, minutesAsInt32, 0);
}
static void Main(string[] args)
{
Console.WriteLine(ParseDuration("30d"));
Console.WriteLine(ParseDuration("12h"));
Console.WriteLine(ParseDuration("20m"));
Console.WriteLine(ParseDuration("1d 12h"));
Console.WriteLine(ParseDuration("5d 30m"));
Console.WriteLine(ParseDuration("1d 12h 20m"));
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
}
编辑:下面是上面的一个替代,稍微更简洁的版本,虽然我不确定哪个更喜欢。我通常不喜欢过于密集的代码。
我调整了正则表达式,每个数字都限制了10位数。这允许我安全地使用int.Parse
函数,因为我知道输入包含至少一个数字且最多十个(除非它根本没有捕获,在这种情况下它将是空字符串:因此,ParseInt32ZeroIfNullOrEmpty方法的目的)。
// Below is a breakdown of this regular expression:
// First, one to ten digits followed by "d" or "D" to represent days.
// Second, one to ten digits followed by "h" or "H" to represent hours.
// Third, one to ten digits followed by "m" or "M" to represent minutes.
// Each component can be separated by any number of spaces, or none.
private static readonly Regex DurationRegex = new Regex(@"((?<Days>\d{1,10})d)?\s*((?<Hours>\d{1,10})h)?\s*((?<Minutes>\d{1,10})m)?", RegexOptions.IgnoreCase);
private static int ParseInt32ZeroIfNullOrEmpty(string input)
{
return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
}
public static TimeSpan ParseDuration(string input)
{
var match = DurationRegex.Match(input);
return new TimeSpan(
ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
0);
}
编辑:为了更进一步,我在下面添加了另一个版本,它处理天,小时,分钟,秒和毫秒,每个版本都有各种缩写。为了便于阅读,我将正则表达式拆分为多行。注意,我还必须在每个组件的末尾使用(\b|(?=[^a-z]))
来调整表达式:这是因为“ms”单元被捕获为“m”单元。与“[^ a-z]”一起使用的“?=”的特殊语法表示匹配字符但不“消耗”它。
// Below is a breakdown of this regular expression:
// First, one to ten digits followed by "d", "dy", "dys", "day", or "days".
// Second, one to ten digits followed by "h", "hr", "hrs", "hour", or "hours".
// Third, one to ten digits followed by "m", "min", "minute", or "minutes".
// Fourth, one to ten digits followed by "s", "sec", "second", or "seconds".
// Fifth, one to ten digits followed by "ms", "msec", "millisec", "millisecond", or "milliseconds".
// Each component may be separated by any number of spaces, or none.
// The expression is case-insensitive.
private static readonly Regex DurationRegex = new Regex(@"
((?<Days>\d{1,10})(d|dy|dys|day|days)(\b|(?=[^a-z])))?\s*
((?<Hours>\d{1,10})(h|hr|hrs|hour|hours)(\b|(?=[^a-z])))?\s*
((?<Minutes>\d{1,10})(m|min|minute|minutes)(\b|(?=[^a-z])))?\s*
((?<Seconds>\d{1,10})(s|sec|second|seconds)(\b|(?=[^a-z])))?\s*
((?<Milliseconds>\d{1,10})(ms|msec|millisec|millisecond|milliseconds)(\b|(?=[^a-z])))?",
RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
private static int ParseInt32ZeroIfNullOrEmpty(string input)
{
return string.IsNullOrEmpty(input) ? 0 : int.Parse(input);
}
public static TimeSpan ParseDuration(string input)
{
var match = DurationRegex.Match(input);
return new TimeSpan(
ParseInt32ZeroIfNullOrEmpty(match.Groups["Days"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Hours"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Minutes"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Seconds"].Value),
ParseInt32ZeroIfNullOrEmpty(match.Groups["Milliseconds"].Value));
}
答案 1 :(得分:3)
<强>更新强> 不要为此投票。我离开它只是因为它是一种替代方法。而是看看sa_ddam213和Dr. Wily的Apprentice的答案。
我应该将字符串中的后缀(在本例中为“d”)分开 首先,然后尝试使用switch语句在枚举中匹配它?
是
一个完整的例子:
private void button1_Click( object sender, EventArgs e ) {
String value = "30d";
Duration d = (Duration)Enum.Parse(typeof(Duration), value.Substring(value.Length - 1, 1).ToUpper());
DateTime result = d.From(new DateTime(), value);
MessageBox.Show(result.ToString());
}
enum Duration { D, W, M, Y };
static class DurationExtensions {
public static DateTime From( this Duration duration, DateTime dateTime, Int32 period ) {
switch (duration)
{
case Duration.D: return dateTime.AddDays(period);
case Duration.W: return dateTime.AddDays((period*7));
case Duration.M: return dateTime.AddMonths(period);
case Duration.Y: return dateTime.AddYears(period);
default: throw new ArgumentOutOfRangeException("duration");
}
}
public static DateTime From( this Duration duration, DateTime dateTime, String fullValue ) {
Int32 period = Convert.ToInt32(fullValue.ToUpper().Replace(duration.ToString(), String.Empty));
return From(duration, dateTime, period);
}
}
答案 2 :(得分:2)
我真的不知道在这里使用enum
有何帮助。
以下是我如何处理它。
string s = "30d";
int typeIndex = s.IndexOfAny(new char[] { 'd', 'w', 'm' });
if (typeIndex > 0)
{
int value = int.Parse(s.Substring(0, typeIndex));
switch (s[typeIndex])
{
case 'd':
result = DateTime.Now.AddDays(value);
break;
case 'w':
result = DateTime.Now.AddDays(value * 7);
break;
case 'm':
result = DateTime.Now.AddMonths(value);
break;
}
}
根据输入数据的可靠性,您可能需要使用int.TryParse()
而不是int.Parse()
。否则,这应该就是你所需要的。
注意:我还为.NET编写了sscanf()
替换,可以很容易地处理这个问题。您可以在文章A sscanf() Replacement for .NET中看到相应的代码。
答案 3 :(得分:1)
尝试以下代码,假设像“30d”这样的值在字符串'val'中。
DateTime ConvertValue(string val) {
if (val.Length > 0) {
int prefix = Convert.ToInt32(val.Length.Remove(val.Length-1));
switch (val[val.Length-1]) {
case 'd': return DateTime.Now.AddDays(prefix);
case 'm': return DateTime.Now.AddMonths(prefix);
// etc.
}
throw new ArgumentException("string in unexpected format.");
}
答案 4 :(得分:1)
控制台应用程序示例/教程的示例:
enum DurationType
{
[DisplayName("m")]
Min = 1,
[DisplayName("h")]
Hours = 1 * 60,
[DisplayName("d")]
Days = 1 * 60 * 24
}
internal class Program
{
private static void Main(string[] args)
{
string input1 = "10h";
string input2 = "1d10h3m";
var x = GetOffsetFromDate(DateTime.Now, input1);
var y = GetOffsetFromDate(DateTime.Now, input2);
}
private static Dictionary<string, DurationType> suffixDictionary
{
get
{
return Enum
.GetValues(typeof (DurationType))
.Cast<DurationType>()
.ToDictionary(duration => duration.GetDisplayName(), duration => duration);
}
}
public static DateTime GetOffsetFromDate(DateTime date, string input)
{
MatchCollection matches = Regex.Matches(input, @"(\d+)([a-zA-Z]+)");
foreach (Match match in matches)
{
int numberPart = Int32.Parse(match.Groups[1].Value);
string suffix = match.Groups[2].Value;
date = date.AddMinutes((int)suffixDictionary[suffix]);
}
return date;
}
}
[AttributeUsage(AttributeTargets.Field)]
public class DisplayNameAttribute : Attribute
{
public DisplayNameAttribute(String name)
{
this.name = name;
}
protected String name;
public String Name { get { return this.name; } }
}
public static class ExtensionClass
{
public static string GetDisplayName<TValue>(this TValue value) where TValue : struct, IConvertible
{
FieldInfo fi = typeof(TValue).GetField(value.ToString());
DisplayNameAttribute attribute = (DisplayNameAttribute)fi.GetCustomAttributes(typeof(DisplayNameAttribute), false).FirstOrDefault();
if (attribute != null)
return attribute.Name;
return value.ToString();
}
}
使用属性来定义后缀,使用枚举值来定义偏移量。
需要:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.RegularExpressions;
使用枚举整数值可能会被认为是一个黑客攻击,但是这个例子仍然可以让你解析掉所有的枚举(对于任何其他用途,比如switch case),只需要很少的调整。
答案 5 :(得分:0)
无法使用非数字类型支持枚举,因此基于字符串的枚举不可用。你可能会过度思考它。在不了解问题的情况下,最直接的解决方案似乎是将最后一个字符拆分,将其余字符转换为int,然后将每个最终字符作为单独的案例处理。
答案 6 :(得分:0)
我建议先使用regexp去除数字,然后执行Enum.Parse Method来评估枚举的值。您可以使用开关(请参阅Corylulu的答案)根据解析的数字和枚举值获得正确的偏移量。