[Abstract] Sample code for converting "\/Date(1270051200000)\/" JSON format to .NET DateTime structure, it is used to convert log files with JSON-serialized data to make the date information readable.

專案裡有個Log機制會以JSON格式保存呼叫歷程中的參數物件,以便事後偵錯及追蹤之用。不過有個小困擾: 由於該Log模組採用JavaScriptSerializer進行序列化,DateTime型別會被格式化成"\/Date(1270051200000)\/"這種微軟獨創的日期表示格式(相關細節可參考舊文),在使用肉眼查看Log檔時,很難將其還原回時間,增加偵錯的難度。

我的構想是寫一個命令列轉檔程式,讀入Log檔並使用Regular Expression找出其中出現的"\/Date(1270051200000)\/"格式,將其置換成"2010/04/01T00:00:00Z"字串後輸出或另存檔案,用轉換後的檔案進行分析,即可避免JSON日期難以閱讀的問題。

在Javascript中,可輕易透過new Date(1270051200000)將"\/Date(1270051200000)\/"轉換成日期,在.NET內將其還原回DateTime時,則有一點小訣竅: Javascript中的tick是由1970/01/01 00:00:00起算的微秒(ms)數,而.NET的Ticks則是從0000/01/01 00:00:00起累計的100奈秒數(10ns, 等於千萬分之一秒),因此要經過轉換(乘上10,000再加上 621355968000000000, 1970/01/01 00:00:00的Ticks)才能算出正確時間。

以下是程式範例: (若要處理的檔案很大,用StringBuilder串接再一次寫入的做法會消耗大量記憶體,可考量改用StreamWriter讀一行寫一行的Streaming做法)

using System;
using System.Text.RegularExpressions;
using System.IO;
using System.Text;
 
namespace Darkthread
{
    class Program
    {
        //getTime() in Javascript is milliseconds since January 1, 1970
        //Ticks in .NET is nanoseconds since January 1, 0000
        //new DateTime(1970, 1, 1).Ticks = 621355968000000000
        static long baseTicks = 621355968000000000;
        //Convert Javascript ticks to DateTime
        static string GetDateTimeString(long jsTicks, int utcDiff = 0)
        {
            return new DateTime(jsTicks * 10000 + baseTicks).AddHours(utcDiff)
                       .ToString("yyyy/MM/ddTHH:mm:ssZ");
        }
        //Convert JSON date string to DateTime
        static string GetDateTimeString(string jsonDateString)
        {
            int p = jsonDateString.IndexOf("(") + 1;
            string numStr = jsonDateString.Substring(p,
                    jsonDateString.IndexOf(")") - p
                );
            int utcDiff = 0;
            string[] a = numStr.Split('+', '-');
            if (a.Length == 2)
            {
                utcDiff = (numStr.Contains("-") ? -1 : 1) * 
                    int.Parse(a[1].Substring(0, 2));
                numStr = a[0];
            }
            return GetDateTimeString(long.Parse(numStr), utcDiff);
        }
 
        static void Main(string[] args)
        {
            if (args.Length < 1 || args.Length > 2)
            {
                Console.WriteLine("Syntax: ConvJsonDate file_name or " +
                                  "ConvJsonDate file_name_*.ext output_path");
                return;
            }
            string fn = args[0];
            string[] files = new string[] { fn };
            //for wildcard search
            if (fn.Contains("*"))
            {
                string srcFolder = Path.GetDirectoryName(fn);
                //if no path assigned, use current folder
                if (string.IsNullOrEmpty(srcFolder))
                    srcFolder = ".";
                string pattern = Path.GetFileName(fn);
                files = Directory.GetFiles(srcFolder, pattern);
            }
            //get output path if assigned
            string path = args.Length > 1 ? args[1] : null;
            //define regular expression pattern
            Regex re = new Regex(@"\\/Date\([0-9+-]+\)\\/");
            StringBuilder sb = new StringBuilder();
            foreach (string f in files)
            {
                using (StreamReader sr = new StreamReader(f))
                {
                    string line;
                    sb.Length = 0;
                    while ((line = sr.ReadLine()) != null)
                        //convert \/Date(....)\/ to yyyy-MM-ddTHH:mm:ssZ
                        sb.AppendLine(
                            re.Replace(line, (o) => GetDateTimeString(o.Value)));
                    
                    if (string.IsNullOrEmpty(path))
                        //if no path assigned, output the converted text to console
                        Console.Write(sb.ToString());
                    else
                        //if path is assigned, dump the converted text to files
                        try
                        {
                            File.WriteAllText(Path.Combine(path, Path.GetFileName(f)), 
                                sb.ToString());
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                            return;
                        }
                }
            }
        }
    }
}

將程式編譯成JConvJsonDate.exe後,有以幾種應用方式:

  1. ConvJsonDate.exe boo.log
    進行JSON日期轉換後顯示檔案內容
  2. ConvJsonDate.exe boo.log > foo.log
    對boo.log進行JSON日期轉換後將結果存為foo.log
  3. ConvJsonDate.exe x:\logs\*.log d:\ConvLogs
    對x:\logs目錄下的所有*.log進行轉換,並將轉換結果以同樣檔名寫入d:\ConvLogs

Comments

Be the first to post a comment

Post a comment