0

The problem is there does not seem to be a built-in way to get a DateTime from a previously calculated "week number".

What I want to do is basically have this work for all times and all cultural calendars.

  DateTime dt1 = new DateTime.Now;
  int week = cal.GetWeekOfYear(dt);
  DateTime dt2 = GetDateTimeFromYearAndWeek(dt.Year, week);

  if (dt1 < dt2 || dt2 > dt2.AddDays(7.0))
  {
    throw new Exception("new datetime limits do not span existing datetime;
  }

I think one of the issues is that for some cultures, the cal.GetWeekOfYear(dt) call will return the correct week number for a different year from dt.Year. That means you can't use it in the call to my fictious GetDateTimeFromYearAndWeek call.

Mark Lakata
  • 19,989
  • 5
  • 106
  • 123

1 Answers1

0

My own answer is three-fold. First, I came up with a wrapper to Calendar.GetWeekOfYear that returns the "year" of the week, since this year may not be the same as the year of the DateTime object.

    public static void GetWeek(DateTime dt, CultureInfo ci, out int week, out int year)
    {
        year = dt.Year;
        week = ci.Calendar.GetWeekOfYear(dt, ci.DateTimeFormat.CalendarWeekRule, ci.DateTimeFormat.FirstDayOfWeek);

        int prevweek = ci.Calendar.GetWeekOfYear(dt.AddDays(-7.0), ci.DateTimeFormat.CalendarWeekRule, ci.DateTimeFormat.FirstDayOfWeek);

        if (prevweek + 1 == week) {
            // year of prevweek should be correct
            year = dt.AddDays(-7.0).Year;
        } else {
            // stay here
            year = dt.Year;
        }
    }

Next, here is the meat of the answer. This inverts the year and weekOfYear back into a DateTime object. Note that I used the middle of the year, because this seems to avoid the problems with the singularities of the new year (where 52 or 53 may or not wrap around to 1). I also synchronize the date by finding a date that is indeed the first day of the week, avoiding problems with negative offsets comparing two DayOfWeek values.

    public static DateTime FirstDateOfWeek(int year, int weekOfYear, CultureInfo ci)
    {
        DateTime jul1 = new DateTime(year, 7, 1);
        while (jul1.DayOfWeek != ci.DateTimeFormat.FirstDayOfWeek)
        {
            jul1 = jul1.AddDays(1.0);
        }
        int refWeek = ci.Calendar.GetWeekOfYear(jul1, ci.DateTimeFormat.CalendarWeekRule, ci.DateTimeFormat.FirstDayOfWeek);

        int weekOffset = weekOfYear - refWeek;

        return jul1.AddDays(7 * weekOffset );
    }

And finally to all those who doubt it, here is my unit test that cycles through lots of dates and cultures to make sure it works on all of them.

    public static void TestDates()
    {
        foreach (CultureInfo ci in CultureInfo.GetCultures(CultureTypes.AllCultures).Where((x)=>!x.IsNeutralCulture && x.Calendar.AlgorithmType == CalendarAlgorithmType.SolarCalendar))
        {

            for (int year = 2010; year < 2040; year++)
            {
                // first try a bunch of hours in this year
                // convert from date -> week -> date

                for (int hour = 0; hour < 356 * 24; hour+= 6)
                {
                    DateTime dt = new DateTime(year, 1, 1).AddHours(hour);
                    int ww;
                    int wyear;
                    Gener8.SerialNumber.GetWeek(dt, ci, out ww, out wyear);
                    if (wyear != year)
                    {
                        //Console.WriteLine("{0} warning: {1} {2} {3}", ci.Name, dt, year, wyear);
                    }
                    DateTime dt1 = Gener8.SerialNumber.FirstDateOfWeek(wyear, ww, ci);
                    DateTime dt2 = Gener8.SerialNumber.FirstDateOfWeek(wyear, ww, ci).AddDays(7.0);
                    if (dt < dt1 || dt > dt2)
                    {
                        Console.WriteLine("{3} Bad date {0} not between {1} and {2}", dt, dt1, dt2, ci.Name);
                    }
                }

                // next try a bunch of weeks in this year
                // convert from week -> date -> week

                for (int week = 1; week < 54; week++)
                {
                    DateTime dt0 = FirstDateOfWeek(year, week, ci);
                    int ww0;
                    int wyear0;
                    GetWeek(dt0, ci, out ww0, out wyear0);
                    DateTime dt1 = dt0.AddDays(6.9);
                    int ww1;
                    int wyear1;
                    GetWeek(dt1, ci, out ww1, out wyear1);
                    if ((dt0.Year == year && ww0 != week) ||
                        (dt1.Year == year && ww1 != week))
                    {
                        Console.WriteLine("{4} Bad date {0} ww0={1} ww1={2}, week={3}", dt0, ww0, ww1, week, ci.Name);
                    }
                }
            }
        }
    }
Mark Lakata
  • 19,989
  • 5
  • 106
  • 123