.NETでのISO 8601の取扱

日付フォーマット yyyy と YYYY の違い – 強火で進め.

なんかはてブで上の記事が盛り上がっていていた。内容的にはISO 8601の規程だと2014/12/29(月)は2015年第1週扱いになるので、MacのObj-Cだとフォーマットの仕方で年が2015年になるというのがその主旨。.NET FrameworkではISO 8601もちゃんとしていないのは容易に想像がつくので、「正しい」ISO 8601仕様通りにするにはどうすれば良いかちょっと試してみました。

まぁ、元ネタみたいのもあって、以下のMSDN Blogにおいても多少書かれています。

ISO 8601 Week of Year format in Microsoft .Net – I’m not a Klingon ( ) – Site Home – MSDN Blogs

今回の肝は、その日が新年の1周目になるかどうかなので、13行目からのGetIso8601WeekOfYearメソッドが一番重要です。月曜日から水曜日までだった日付に3日間を足して、週番号が変わるのは、年をまたいだときだけです。

GetIso8601DateTimeStringは、Obj-Cと同じ仕様で年の値が変わるようにしたものです。秒以下の書式をString.Format(“O”, obj)としたときに合わせていますが、ISO 8601で秒以下の書式は小数点表記でしか無いようなのでこうしておけば良いかなと行ったところ。ま、その前にISO 8601でもこの年月日日時表記の時にはあくまでも2014年だと思う。

GetIso8601WeekDatesStringは、何年何週目何曜日をISO 8601の書式で出力する物で、本来このときに初めて2014/12/29でも年を2015年とすべきだと思うんですよね。

コード:

namespace DateTimeCheck
{
    using System;
    using System.Globalization;

    internal class Iso8601Check
    {
        // Need a calendar.  Culture's irrelevent since we specify start day of week
        private static Calendar cal = CultureInfo.InvariantCulture.Calendar;

        // Original Code: http://blogs.msdn.com/b/shawnste/archive/2006/01/24/iso-8601-week-of-year-format-in-microsoft-net.aspx
        // (c) Copy right 2006 Shawn Steele
        public static int GetIso8601WeekOfYear(DateTime time)
        {
            DayOfWeek day = cal.GetDayOfWeek(time);
            if (day >= DayOfWeek.Monday && day <= DayOfWeek.Wednesday)
            {
                time = time.AddDays(3);
            }

            // Return the week of our adjusted day
            return cal.GetWeekOfYear(time, CalendarWeekRule.FirstFourDayWeek, DayOfWeek.Monday);
        }

        /// 
        /// ISO 8601の仕様で引数で与えられた日時の年を取得する。
        /// 
        /// 変換する日時
        /// 
        public static int GetIso8601Year(DateTime date)
        {
            var year = date.Year;
            if (12 == date.Month)
            {
                if (1 == GetIso8601WeekOfYear(date))
                {
                    year++;
                }
            }

            return year;
        }

        /// 
        /// ISO 8601の仕様での日付の文字列を取得する。
        /// YYYY-MM-DDThh:mm:ss.0000000±hh:mm
        /// 
        /// 変換する日時
        /// 書式化された文字列
        public static string GetIso8601DateTimeString(DateTime date)
        {
            var year = GetIso8601Year(date);
            var subTime = date - new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second);
            return String.Format(
                "{0:00}-{1:00}-{2:00}T{3:00}:{4:00}:{5:00}.{6:0000000}{7:zzz}",
                year,
                date.Month,
                date.Day,
                date.Hour,
                date.Minute,
                date.Second,
                (subTime.Ticks * 10),
                date);
        }

        /// 
        /// ISO 8601の仕様で年と週と曜日の文字列を取得する.
        /// YYYY-Www-D
        /// 
        /// 変換する日時
        /// 書式化された文字列
        public static string GetIso8601WeekDatesString(DateTime date)
        {
            var year = GetIso8601Year(date);
            var week = GetIso8601WeekOfYear(date);
            var dayOfWeek = date.DayOfWeek == DayOfWeek.Sunday ? 7 : (int)date.DayOfWeek;
            return string.Format("{0:00}-W{1:00}-{2}", year, week, dayOfWeek);
        }


        private static void Main()
        {
            // ISO 8601では週は月曜日から始まるので2014-12-28(日)はまだ2014年
            Console.WriteLine("ISO 8601では週は月曜日から始まるので2014-12-28(日)はまだ2014年");
            var testDate = new DateTime(2014, 12, 9, 0, 0, 0, 0);
            Console.WriteLine("{0:O}", testDate);
            Console.WriteLine("ISO 8601での年 " + GetIso8601Year(testDate));
            Console.WriteLine("ISO 8601での週番号 " + GetIso8601WeekOfYear(testDate));
            Console.WriteLine("ISO 8601 " + GetIso8601DateTimeString(testDate));
            Console.WriteLine("ISO 8601dの年と週と曜日 " + GetIso8601WeekDatesString(testDate));
            
            // 2014-12-29(月)は木曜日が2015-01-01なので2015年の第1週となるはず。
            Console.WriteLine("2014-12-29(月)は木曜日が2015-01-01なので2015年の第1週となるはず。");
            testDate = testDate.AddDays(1.0);
            Console.WriteLine("{0:O}", testDate);
            Console.WriteLine("ISO 8601での年 " + GetIso8601Year(testDate));
            Console.WriteLine("ISO 8601での週番号 " + GetIso8601WeekOfYear(testDate));
            Console.WriteLine("ISO 8601 " + GetIso8601DateTimeString(testDate));
            Console.WriteLine("ISO 8601dの年と週と曜日 " + GetIso8601WeekDatesString(testDate));
        }
    }
}

最後にどうでも良いのですが、2014-12-28は日曜日なので、ISO 8601に従うならば、2014年52週7日目となります。あくまでも2014年のはずです。先頭Blog記事通り、ISO 8601のフォーマットにして2015年になるとしたら、MacもiOSデバイスも大丈夫なんでしょうか。そもそもなんかやり過ぎて仕様バグ埋め込んでいるというか、よくわからないバグを埋め込みやすくしている気がします。

コメントを残す