如何将DateTimeOffset序列化为二进制流

时间:2016-07-31 11:55:22

标签: c# .net base-class-library

我想知道将DateTimeOffset序列化为二进制流(使用BinaryWriter)并再次反序列化(使用BinaryReader)的最佳方法是什么。

要序列化DateTime,我有:

    public void WriteValue(DateTime value)
    {
        _writer.Write(value.ToBinary());
    }

    public DateTime ReadDateTime()
    {
        return DateTime.FromBinary(_reader.ReadInt64());
    }

在性能和存储大小方面序列化/反序列化DateTimeOffset的最佳方法是什么?

3 个答案:

答案 0 :(得分:2)

根据Hans Passant的评论,我提出了以下解决方案。序列化:

    public void WriteValue(DateTimeOffset value)
    {
        WriteValue(value.DateTime);
        WriteValue((short)value.Offset.TotalMinutes);
    }

反序列化:

    public DateTimeOffset ReadDateTimeOffset()
    {
        var dateTime = ReadDateTime();
        var minutes = ReadInt16();
        return new DateTimeOffset(dateTime, TimeSpan.FromMinutes(minutes));
    }

因此,这些方法会调用问题中概述的DateTime的现有序列化方法。

我仍然想知道这是否是最有效的方法。调用TotalMinutesTimeSpan.FromMinutes的速度有多快?

答案 1 :(得分:1)

不是说我的回答增加了很多,但我已经编写了这个小实用程序来打包和解压缩DateTimeOffset:

public static class DateTimeOffsetExtensions
{
    /// <summary>
    /// Packs DateTimeOffset to bytes
    /// </summary>
    /// <param name="dateTimeOffset"></param>
    /// <returns>10 byte packed bytearray</returns>
    public static byte[] GetBytes(this DateTimeOffset dateTimeOffset)
    {
        return BitConverter.GetBytes(dateTimeOffset.Ticks).
           Concat(BitConverter.GetBytes((Int16)dateTimeOffset.Offset.TotalMinutes)).ToArray();
    }
    /// <summary>
    /// Reads 10 bytes from a buffer and turns back to DateTimeOffset
    /// </summary>
    /// <param name="bytes">Buffer</param>
    /// <param name="offset">Offset to read from</param>
    /// <returns></returns>
    public static DateTimeOffset FromBytes(byte[] bytes, int offset)
    {
        var ticks = BitConverter.ToInt64(bytes, offset);
        var offsetMinutes = BitConverter.ToInt16(bytes, offset + 8);
        return new DateTimeOffset(ticks, TimeSpan.FromMinutes(offsetMinutes));
    }
}

答案 2 :(得分:-1)

序列化和反序列化Date-Time对象(任何语言)的正确方法是使用时区和夏令时独立值,例如UTC时间。在反序列化时,要么保留在UTC中(如果仅由代码使用),要么选择当前的本地时区偏移量(如果由人类读取)。这样可以避免不同时区的问题,即使值保留在一个国家/地区,也可以避免在序列化/反序列化之间夏令时发生变化时出现问题。

DateTimeOffset.Ticks属性(64位整数)是二进制序列化器的理想选择。请注意不要使用DateTimeOffset dto = DateTimeOffset.Now; using (var w = new BinaryWriter(...)) { w.Write(dto.UtcTicks); // do not use dto.Ticks! } ,因为此属性包含任何偏移。

DateTimeOffset

反序列化看起来很笨拙,因为没有简单的方法可以从UTC刻度创建using (var r = new BinaryReader(...)) { long utcTicks = r.ReadInt64(); dto = new DateTimeOffset(utcTicks, TimeSpan.Zero).ToLocalTime(); } 。如果对象从未向人类显示,则可以跳过转换为当地时间。

DateTimeOffset dto = DateTimeOffset.Now;
using (var w = new BinaryWriter(...))
{
    w.Write(dto.Ticks);
    w.Write(dto.Offset.Ticks);
}

using (var r = new BinaryReader(...))
{
    long ticks = r.ReadInt64();
    long offsetTicks = r.ReadInt64();
    dto = new DateTimeOffset(ticks, new TimeSpan(offsetTicks);
}

就存储而言,您需要8个字节= 64位。我不确定反序列化性能(未经测试),但我认为它应该尽可能快。

编辑:保留时区(偏移)信息,

package testjpanelexample;

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class TestJPanelExample {

/**
 * @param args the command line arguments
 */
public static void main(String[] args) 
{
    // TODO code application logic here
    TestJPanelExample test = new TestJPanelExample("Test");
}
private final int FRAME_WIDTH = 500;
private final int FRAME_HEIGHT = 500;
private final JFrame gameWindow;
private final TestDrawingPanel myDrawingPanel;
private final TestStartGame startGame;
private final JPanel cards;
private final CardLayout cl1;
private static final String DRAWINGPANEL = "DrawingPanel";
private static final String STARTGAME = "StartGame";

//creates a JFrame and all the JPanels and adds them to a CardLayout
public TestJPanelExample(String title)
{               
    gameWindow = new JFrame(title);
    gameWindow.setSize(FRAME_WIDTH, FRAME_HEIGHT);
    cards = new JPanel(new CardLayout());
    startGame = new TestStartGame(FRAME_WIDTH, FRAME_HEIGHT);
    startGame.addAL(new StartButton());
    myDrawingPanel = new TestDrawingPane(FRAME_WIDTH, FRAME_HEIGHT);
    gameWindow.add(cards);
    cards.add(startGame, STARTGAME);
    cards.add(myDrawingPanel, DRAWINGPANEL);
    cl1 = (CardLayout)(cards.getLayout());
    gameWindow.setVisible(true);
    gameWindow.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}


//starts the game running
public void startTheGame()
{
    cl1.show(cards, DRAWINGPANEL);
    update();
}

//switches back to the start screen    
public void update()
{
    cl1.show(cards, STARTGAME);
}    

//an action listener to be added to the start screen
private class StartButton implements ActionListener
{
    public void actionPerformed(ActionEvent e)
    {           
            startTheGame();
    }
}


//The opening screen
class TestStartGame extends JPanel 
{

private JPanel buttons;
public  JButton start;

//creates a JPanel with a single button to start the game    
public TestStartGame(int width, int height)
{
    setSize(width, height);
    setBackground(Color.GREEN);
    start = new JButton("Start");
    add(start);
}            

//adds an action listener to the button    
public void addAL(ActionListener al)
{
    start.addActionListener(al);
}   
}

//The game screen (currently just a blank blue screen)
class TestDrawingPanel extends JPanel{

//creates the drawing panel 
public TestDrawingPanel(int width, int height)
{                
    setSize(width, height);
    this.setBackground(Color.CYAN);
}      
} 
}