redsoxfantom
redsoxfantom

Reputation: 956

DateTime parse string containing day of year

I'm trying to parse a string formatted like so:

1900-001T00:00:00Z

into a DateTime object. The middle bit there (right after the "1900-" and before the "T") is supposed to be the day of year. I know the rest of the formatting string I would need to use would be

yyyy-XXXTHH:mm:ssZ

but what should I put in for that 'XXX'?

Upvotes: 2

Views: 1091

Answers (3)

Rufus L
Rufus L

Reputation: 37020

I'm sure there's a fancier way, but you could write your own method to do it:

private static DateTime CustomParseDayOfYear(string input)
{
    if (input == null) throw new ArgumentNullException(nameof(input));

    var parts = input.Split('-', 'T');
    if (parts.Length != 3) throw new FormatException(nameof(input));

    var timeParts = parts[2].Trim('Z').Split(':');
    if (timeParts.Length != 3) throw new FormatException(nameof(input));

    int hour, minute, second, year, dayOfYear;

    if (!int.TryParse(parts[0], out year))
        throw new FormatException("Year must be an integer");
    if (!int.TryParse(parts[1], out dayOfYear))
        throw new FormatException("DayOfYear must be an integer");
    if (!int.TryParse(timeParts[0], out hour))
        throw new FormatException("Hour must be an integer");
    if (!int.TryParse(timeParts[1], out minute))
        throw new FormatException("Minute must be an integer");
    if (!int.TryParse(timeParts[2], out second))
        throw new FormatException("Second must be an integer");

    var maxDayOfYear = new DateTime(year, 12, 31).DayOfYear;

    if (year < 1 || year > 9999)
        throw new ArgumentOutOfRangeException(
            "Year must be greater than zero and less than 10000");
    if (dayOfYear < 1 || dayOfYear > maxDayOfYear)
        throw new ArgumentOutOfRangeException(
            $"DayOfYear must be greater than zero and less than {maxDayOfYear + 1}");
    if (hour > 23) throw new ArgumentOutOfRangeException($"Hour must be less than 24");
    if (minute > 59) throw new ArgumentOutOfRangeException($"Minute must less than 60");
    if (second > 59) throw new ArgumentOutOfRangeException($"Second must less than 60");

    return new DateTime(year, 1, 1, hour, minute, second).AddDays(dayOfYear - 1);
}

Upvotes: 2

Richardissimo
Richardissimo

Reputation: 5763

Based on @AlexK's suggestion, here you go, nice and simple...

using System.Globalization;
using System.Text.RegularExpressions;

    private DateTime? ParseDayOfYearDate(string value)
    {
        DateTime? result = null;
        Regex dayOfYearDatePattern = new Regex(@"^(\d+\-)(\d+)(.+)$");
        Match dayOfYearDateMatch = dayOfYearDatePattern.Match(value);
        if (dayOfYearDateMatch.Success)
        {
            string altered = dayOfYearDateMatch.Groups[1].Value + "01-01" + dayOfYearDateMatch.Groups[3].Value;
            int dayOfYear = int.Parse(dayOfYearDateMatch.Groups[2].Value); // will succeed due to the definition of the pattern
            DateTime startOfYear = DateTime.ParseExact(altered, "yyyy-MM-ddTHH:mm:ssK", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);
            result = startOfYear.AddDays(dayOfYear - 1); // since we already gave it 1st January
        }
        else
        {
            // It didn't match the pattern, will return null.
        }

        return result;
    }

Upvotes: 1

Patrick Artner
Patrick Artner

Reputation: 51643

A self written parser could look like this:

static DateTime ToDt(string date)
{
    var splitYear = date.Split(new[] { '-' }, StringSplitOptions.RemoveEmptyEntries);
    var splitDays = splitYear[1].Split(new[] { 'T' }, StringSplitOptions.RemoveEmptyEntries);
    var hms = splitDays[1].TrimEnd('Z').Split(':');

    var dt = new DateTime(int.Parse(splitYear[0]), 1, 1, 0, 0, 0);
    dt = dt.AddDays(int.Parse(splitDays[0]) - 1);
    dt = dt.AddHours(int.Parse(hms[0]));
    dt = dt.AddMinutes(int.Parse(hms[1]));
    dt = dt.AddSeconds(int.Parse(hms[2]));

    return dt;
}

static void Main(string[] args)
{
    Console.WriteLine(ToDt("1900-001T00:10:00Z"));
    Console.WriteLine(ToDt("1923-180T12:11:10Z"));
    Console.WriteLine(ToDt("1979-365T23:59:59Z"));
    Console.WriteLine(ToDt("2017-074T18:47:10Z"));


    Console.ReadLine();
}

Output:

01.01.1900 00:10:00
29.06.1923 12:11:10
31.12.1979 23:59:59
15.03.2017 18:47:10

This will throw if

  • splits are not returning the expected amount of splits (ill formatting) IndexOutOfRangeException
  • numbers are not parsable to int FormatException

and int won't guard against "nonsensical but wellformed" inputs

'2000-999T99:99:99Z'   -->  29.09.2002 04:40:39

Upvotes: 2

Related Questions