41348


DateTimeをシリアライズするためのメソッドDateTime.ToBinary()がありますが、  
DateTime型は日付が含まれるため、日付が必要ない場合に無駄になってしまいます。

（Hour以下のTicksの最大値は863,999,999,999なので5バイトで表現可能。  
少なくとも今の私の仕事では8バイトと5バイトの差は大きいです）

時刻のみをシリアライズできるようなライブラリが見当たらなかったため、  
とりあえず作ってみたものが以下になります。

[code]

    class Program
    {
        static void Main()
        {
            var now = DateTime.UtcNow;
            var bytes = TimeSerializer.Serialize(now);
            var now2 = TimeSerializer.Deserialize(bytes, now.Date);
    
            Console.WriteLine(now.ToString("yyyy/MM/dd HH:mm:ss.fffffff"));
            Console.WriteLine(now2.ToString("yyyy/MM/dd HH:mm:ss.fffffff"));
        }
    }
    
    static class TimeSerializer
    {
        public static byte[] Serialize(DateTime time)
        {
            return Serialize((time - time.Date).Ticks);
        }
    
        public static byte[] Serialize(long ticks)
        {
            var bytes = BitConverter.GetBytes(ticks);
            return bytes.Take(5).ToArray();
        }
    
        public static DateTime Deserialize(byte[] bytes, DateTime date)
        {
            return date.AddTicks(Deserialize(bytes));
        }
    
        public static long Deserialize(byte[] bytes)
        {
            var longBytes = new byte[8] { bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], 0, 0, 0 };
            return BitConverter.ToInt64(longBytes, 0);
        }
    }
    
[/code]

これよりマシな方法はありますか？  
またはこういったことができるライブラリはありますか？


CC BY-SA 3.0
2018-01-31T05:26:46.903
41342
2018-01-31T22:35:08.253
2018-01-31T22:35:08.253
4236
21330
question
c# sqlite
時刻のみをシリアライズしたい


サイズを削除したいというのであれば、バイトの配列を利用することは全く無意味です。  
これは、配列には

  1. 配列のサイズ
  2. 配列の型

が必ずオーバーヘッドとして存在し、処理系依存になるので全ての環境で同一のオーバーヘッドが存在するわけではないという点をご了解頂いた上で、一例として32bitの.NET
Frameworkの場合、12byte(96bit)となります。

従って、それに実データの5byteを足すと、最低でも合計17byte(136bit)になってしまい元の64bitの2倍以上のサイズが必要となるでしょう。

従って、提示された運用は64bit以上のサイズを取ってしまうので、速度的な面を見てもまたサイズ的な面を見ても64bitのまま運用するのが最適かと思います。

逆に時刻のみでよく、なおかつ精度を落としてもかまわないと言うことであれば32bitに切り詰めてしまうことで、シュリンクすることは可能だと想います。

32bitにするなら、精度は0.1ms単位となり、16bitの場合は10秒単位当たりが適当かと想います。  
(キリの良い形とするなら)

また、配列が生きてくるパターンとしては、複数の時刻データを単一の配列で運用するのなら、オーバーヘッドは償却可能だとは思いますが、運用的な制約が大きすぎて余りお勧めできるモノでは無いと考えます。


CC BY-SA 3.0
2018-01-31T07:56:15.177
41346
2018-01-31T14:48:43.170
2018-01-31T14:48:43.170
7287
7287
41342
answer


> 時刻のデータがかなりの割合を占めるSQLiteのファイルサイズを抑えるため

このコメントで質問内容がほとんど別物になってしまいました。  
SQLite3の`INTEGER`は

> **INTEGER**. The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8
> bytes depending on the magnitude of the value.

と説明されています。`DateTime.TimeOfDay.Ticks`を格納するだけで値に応じた適切なサイズで格納されます。  
（と言っても大半は6バイトになりそうですが。）

* * *

> こういったことができるライブラリはありますか？

直接的にはありません。ある人は５バイトがよく、またある人は６バイト、別の人は精度を減らして４バイト、など環境により求められるものは様々です。結局は必要な人が必要なものを作るのが一番です。

> これよりマシな方法はありますか？

元コードはlittle endianに依存しています。big
endian環境で先頭５バイトを参照しても意味がありません。また`BitConverter`で８バイト配列を作成してから改めて５バイト配列を作り直すのは非効率です。たった５バイトですし直接操作した方が早く、速いはずです。

[code]

    static class TimeSerializer {
        public static byte[] SerializeTime(this DateTime time) {
            var ticks = time.TimeOfDay.Ticks;
            return new byte[] { (byte)ticks, (byte)(ticks >> 8), (byte)(ticks >> 16), (byte)(ticks >> 24), (byte)(ticks >> 32) };
        }
        public static DateTime Deserialize(byte[] bytes, DateTime date) {
            if (bytes == null)
                throw new ArgumentNullException(nameof(bytes));
            if (bytes.Length < 5)
                throw new ArgumentOutOfRangeException(nameof(bytes));
            var ticks = bytes[0] | (uint)bytes[1] << 8 | (uint)bytes[2] << 16 | (uint)bytes[3] << 24 | (long)bytes[4] << 32;
            return date.AddTicks(ticks);
        }
    }
    
[/code]

> 少なくとも今の私の仕事では8バイトと5バイトの差は大きいです

これは大量のデータを扱うことを意味しての発言でしょうか？
その場合は５バイト配列を何度も作ったり、それを結合したりする行為は効率が悪いので、配列とオフセット値でアクセス可能なオーバーロードも用意した方がいいでしょう。


CC BY-SA 3.0
2018-01-31T08:21:18.197
41348
2018-01-31T22:34:50.147
2018-01-31T22:34:50.147
4236
4236
41342
answer


ストリームに書き込む事を想定しつつ、元のシグネチャも維持するとこんな感じでしょうか。

[code]

    public static class TimeSerializer {
    
        public static byte[] Serialize(DateTime time) {
            var bytes = new byte[5];
            Write(new MemoryStream(bytes), time);
            return bytes;
        }
    
        public static byte[] Serialize(long ticks) {
            var bytes = new byte[5];
            Write(new MemoryStream(bytes), ticks);
            return bytes;
        }
    
        public static DateTime Deserialize(byte[] bytes, DateTime date)
            => Read(new MemoryStream(bytes), date);
    
        public static long Deserialize(byte[] bytes)
            => Read(new MemoryStream(bytes));
    
        #region Core Implementation
    
        private const long TicksOfDay = 864000000000;
    
        /// <summary>
        /// 指定された <paramref name="dateTime"/> のうち、時刻のみをストリームに書き出します。
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="stream"></param>
        public static void Write(Stream stream, DateTime dateTime)
            => Write(stream, dateTime.Ticks);
    
        /// <summary>
        /// 指定された <paramref name="ticks"/> のうち、時刻のみをストリームに書き出します。
        /// </summary>
        /// <param name="ticks"></param>
        /// <param name="stream"></param>
        public static void Write(Stream stream, long ticks) {
            if (stream == null) throw new ArgumentNullException(nameof(stream));
    
            var timeTicks = ticks % TicksOfDay;
    
            stream.WriteByte((byte)(timeTicks & 0xff));
            stream.WriteByte((byte)(timeTicks >> 8 & 0xff));
            stream.WriteByte((byte)(timeTicks >> 16 & 0xff));
            stream.WriteByte((byte)(timeTicks >> 24 & 0xff));
            stream.WriteByte((byte)(timeTicks >> 32 & 0xff));
        }
    
        /// <summary>
        /// 指定された <paramref name="stream"/> と <paramref name="date"/> から、元となった <see cref="DateTime"/> オブジェクトを再構築します。
        /// </summary>
        /// <param name="stream"></param>
        /// <param name="date"></param>
        /// <returns></returns>
        public static DateTime Read(Stream stream, DateTime date)
            => date.Date.AddTicks(Read(stream));
    
        /// <summary>
        /// 指定された <paramref name="stream"/> から時刻の Ticks を読み取ります。
        /// </summary>
        /// <param name="stream"></param>
        /// <returns></returns>
        public static long Read(Stream stream) {
            if (stream == null) throw new ArgumentNullException(nameof(stream));
    
            var timeTicks =
                  (long)stream.ReadByte()
                | (long)stream.ReadByte() << 8
                | (long)stream.ReadByte() << 16
                | (long)stream.ReadByte() << 24
                | (long)stream.ReadByte() << 32
                ;
            return timeTicks % TicksOfDay;
        }
    
        #endregion Core Implementation
    }
    
[/code]

DB 等で永続化する際に省スペース化を図りたいという事であれば、挿入頻度にも依りますが、極端に効率が悪くない限りは何でもいいと思います。  
そういう意味では、質問者さんのコードそのままでも仕様通りに動くのなら問題ないと思います。

よって、

> これよりマシな方法はありますか？

この質問は、何を改善したいかによるんですよね。  
シナリオをより具体化してもらえると、改善案を提案しやすいと思います。


CC BY-SA 3.0
2018-01-31T11:37:13.037
41354
2018-01-31T11:37:13.037
26808
41342
answer