Skip to content

C# 日期型 JSON 序列化后毫秒部分的 7 位数字是啥?

🏷️ C# Newtonsoft.Json

使用 Newtonsoft.Json 来序列化日期型字段时,其毫秒部分有 7 为小数。一直也没太在意,但昨天在 Java 中使用 SimpleDateFormat 反序列化时却发现没有对应的格式说明符。

Newtonsoft.Json 序列化时默认使用了 IsoDateTimeConverter,其序列化后结果为 2019-09-23T15:20:06.4086491+08:00

而 Java 中 SimpleDateFormat 最接近的一种格式为 yyyy-MM-dd'T'HH:mm:ss.SSSXXX,其毫秒部分只有 3 位小数,反序列化上面的日期字符串会报错。

为啥会失败?首先要搞清楚这 7 位数字是啥。请参考 MSDN 的文档:

The "fffffff" custom format specifier

The "fffffff" custom format specifier represents the seven most significant digits of the seconds fraction; that is, it represents the ten millionths of a second in a date and time value.

Although it's possible to display the ten millionths of a second component of a time value, that value may not be meaningful. The precision of date and time values depends on the resolution of the system clock. On the Windows NT 3.5 (and later) and Windows Vista operating systems, the clock's resolution is approximately 10-15 milliseconds.

"fffffff" 表示千万分之一精度的秒,但是后面又说了,在 Windows 中其精度由系统时钟决定,大约在 10-15 毫秒。换句话说,在 Windows 中这个精度是几乎是没有意义的。(不知道在 Linux 中其精度是多少?)

C# 中自定义日期型的序列化方法

既然几乎没意义,那么就可以放心的删掉了。参考 这篇博客,有两种方式可以修改日期型的序列化格式,具体使用哪种方式根据项目需要而定。

  1. 在序列化时通过传递一个 JsonSerializerSettings 来配置日期的序列化格式

    csharp
    JsonConvert.SerializeObject(obj, new JsonSerializerSettings()
    {
        DateFormatString = "yyyy-MM-dd'T'HH:mm:ss.fffzzz",
    });
  2. 在需要自定义格式的日期字段上添加 [JsonConverter(typeof(CustomDateTimeConverter))] 特性

    csharp
    public class CustomDateTimeConverter : IsoDateTimeConverter
    {
        public CustomDateTimeConverter()
        {
            DateTimeFormat = "yyyy-MM-dd'T'HH:mm:ss.fffzzz";
        }
    }

Scala 中的反序列化处理

在 Java 中使用 new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")parse 方法可以正确的转成 Date 型。

这里项目使用的 Genson 的 Scala 版的包。自定义 JSON 反序列化的方法如下:

  1. 定义 CustomGenson.scala 类:

    scala
    import java.text.SimpleDateFormat
    
    import com.owlike.genson._
    
    object CustomGenson {
    val customGenson = new ScalaGenson(
        new GensonBuilder()
        .useDateFormat(new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"))
        .withBundle(ScalaBundle().useOnlyConstructorFields(false))
        .create()
    )
    }
  2. 引用上面自定义的包,反序列化的方法不变。

    scala
    import CustomGenson.customGenson._
    
    fromJson[ModelName](strJson)

C# 中日期型格式化的格式说明符

摘自 MSDN

Format specifierDescriptionExamples
"d"The day of the month, from 1 through 31.2009-06-01T13:45:30 -> 1
2009-06-15T13:45:30 -> 15
"dd"The day of the month, from 01 through 31.2009-06-01T13:45:30 -> 01
2009-06-15T13:45:30 -> 15
"ddd"The abbreviated name of the day of the week.2009-06-15T13:45:30 -> Mon (en-US)
2009-06-15T13:45:30 -> Пн (ru-RU)
2009-06-15T13:45:30 -> lun. (fr-FR)
"dddd"The full name of the day of the week.2009-06-15T13:45:30 -> Monday (en-US)
2009-06-15T13:45:30 -> понедельник (ru-RU)
2009-06-15T13:45:30 -> lundi (fr-FR)
"f"The tenths of a second in a date and time value.2009-06-15T13:45:30.6170000 -> 6
2009-06-15T13:45:30.05 -> 0
"ff"The hundredths of a second in a date and time value.2009-06-15T13:45:30.6170000 -> 61
2009-06-15T13:45:30.0050000 -> 00
"fff"The milliseconds in a date and time value.6/15/2009 13:45:30.617 -> 617
6/15/2009 13:45:30.0005 -> 000
"ffff"The ten thousandths of a second in a date and time value.2009-06-15T13:45:30.6175000 -> 6175
2009-06-15T13:45:30.0000500 -> 0000
"fffff"The hundred thousandths of a second in a date and time value.2009-06-15T13:45:30.6175400 -> 61754
6/15/2009 13:45:30.000005 -> 00000
"ffffff"The millionths of a second in a date and time value.2009-06-15T13:45:30.6175420 -> 617542
2009-06-15T13:45:30.0000005 -> 000000
"fffffff"The ten millionths of a second in a date and time value.2009-06-15T13:45:30.6175425 -> 6175425
2009-06-15T13:45:30.0001150 -> 0001150
"F"If non-zero, the tenths of a second in a date and time value.2009-06-15T13:45:30.6170000 -> 6
2009-06-15T13:45:30.0500000 -> (no output)
"FF"If non-zero, the hundredths of a second in a date and time value.2009-06-15T13:45:30.6170000 -> 61
2009-06-15T13:45:30.0050000 -> (no output)
"FFF"If non-zero, the milliseconds in a date and time value.2009-06-15T13:45:30.6170000 -> 617
2009-06-15T13:45:30.0005000 -> (no output)
"FFFF"If non-zero, the ten thousandths of a second in a date and time value.2009-06-15T13:45:30.5275000 -> 5275
2009-06-15T13:45:30.0000500 -> (no output)
"FFFFF"If non-zero, the hundred thousandths of a second in a date and time value.2009-06-15T13:45:30.6175400 -> 61754
2009-06-15T13:45:30.0000050 -> (no output)
"FFFFFF"If non-zero, the millionths of a second in a date and time value.2009-06-15T13:45:30.6175420 -> 617542
2009-06-15T13:45:30.0000005 -> (no output)
"FFFFFFF"If non-zero, the ten millionths of a second in a date and time value.2009-06-15T13:45:30.6175425 -> 6175425
2009-06-15T13:45:30.0001150 -> 000115
"g", "gg"The period or era.2009-06-15T13:45:30.6170000 -> A.D.
"h"The hour, using a 12-hour clock from 1 to 12.2009-06-15T01:45:30 -> 1
2009-06-15T13:45:30 -> 1
"hh"The hour, using a 12-hour clock from 01 to 12.2009-06-15T01:45:30 -> 01
2009-06-15T13:45:30 -> 01
"H"The hour, using a 24-hour clock from 0 to 23.2009-06-15T01:45:30 -> 1
2009-06-15T13:45:30 -> 13
"HH"The hour, using a 24-hour clock from 00 to 23.2009-06-15T01:45:30 -> 01
2009-06-15T13:45:30 -> 13
"K"Time zone information.2009-06-15T13:45:30, Kind Unspecified ->
2009-06-15T13:45:30, Kind Utc -> Z
2009-06-15T13:45:30, Kind Local -> -07:00 (depends on local computer settings)
2009-06-15T01:45:30-07:00 --> -07:00
2009-06-15T08:45:30+00:00 --> +00:00
"m"The minute, from 0 through 59.2009-06-15T01:09:30 -> 9
2009-06-15T13:29:30 -> 29
"mm"The minute, from 00 through 59.2009-06-15T01:09:30 -> 09
2009-06-15T01:45:30 -> 45
"M"The month, from 1 through 12.2009-06-15T13:45:30 -> 6
"MM"The month, from 01 through 12.2009-06-15T13:45:30 -> 06
"MMM"The abbreviated name of the month.2009-06-15T13:45:30 -> Jun (en-US)
2009-06-15T13:45:30 -> juin (fr-FR)
2009-06-15T13:45:30 -> Jun (zu-ZA)
"MMMM"The full name of the month.2009-06-15T13:45:30 -> June (en-US)
2009-06-15T13:45:30 -> juni (da-DK)
2009-06-15T13:45:30 -> uJuni (zu-ZA)
"s"The second, from 0 through 59.2009-06-15T13:45:09 -> 9
"ss"The second, from 00 through 59.2009-06-15T13:45:09 -> 09
"t"The first character of the AM/PM designator.2009-06-15T13:45:30 -> P (en-US)
2009-06-15T13:45:30 -> 午 (ja-JP)
2009-06-15T13:45:30 -> (fr-FR)
"tt"The AM/PM designator.2009-06-15T13:45:30 -> PM (en-US)
2009-06-15T13:45:30 -> 午後 (ja-JP)
2009-06-15T13:45:30 -> (fr-FR)
"y"The year, from 0 to 99.0001-01-01T00:00:00 -> 1
0900-01-01T00:00:00 -> 0
1900-01-01T00:00:00 -> 0
2009-06-15T13:45:30 -> 9
2019-06-15T13:45:30 -> 19
"yy"The year, from 00 to 99.0001-01-01T00:00:00 -> 01
0900-01-01T00:00:00 -> 00
1900-01-01T00:00:00 -> 00
2019-06-15T13:45:30 -> 19
"yyy"The year, with a minimum of three digits.0001-01-01T00:00:00 -> 001
0900-01-01T00:00:00 -> 900
1900-01-01T00:00:00 -> 1900
2009-06-15T13:45:30 -> 2009
"yyyy"The year as a four-digit number.0001-01-01T00:00:00 -> 0001
0900-01-01T00:00:00 -> 0900
1900-01-01T00:00:00 -> 1900
2009-06-15T13:45:30 -> 2009
"yyyyy"The year as a five-digit number.0001-01-01T00:00:00 -> 00001
2009-06-15T13:45:30 -> 02009
"z"Hours offset from UTC, with no leading zeros.2009-06-15T13:45:30-07:00 -> -7
"zz"Hours offset from UTC, with a leading zero for a single-digit value.2009-06-15T13:45:30-07:00 -> -07
"zzz"Hours and minutes offset from UTC.2009-06-15T13:45:30-07:00 -> -07:00
":"The time separator.2009-06-15T13:45:30 -> : (en-US)
2009-06-15T13:45:30 -> . (it-IT)
2009-06-15T13:45:30 -> : (ja-JP)
"/"The date separator.2009-06-15T13:45:30 -> / (en-US)
2009-06-15T13:45:30 -> - (ar-DZ)
2009-06-15T13:45:30 -> . (tr-TR)
"string"Literal string delimiter.2009-06-15T13:45:30 ("arr:" h:m t) -> arr: 1:45 P
2009-06-15T13:45:30 ('arr:' h:m t) -> arr: 1:45 P
%Defines the following character as a custom format specifier.2009-06-15T13:45:30 (%h) -> 1
\The escape character.2009-06-15T13:45:30 (h \h) -> 1 h
Any other characterThe character is copied to the result string unchanged.2009-06-15T01:45:30 (arr hh:mm t) -> arr 01:45 A

Java 中日期型格式化的格式说明符

摘自 SimpleDateFormat 的注释。

LetterDate or Time ComponentPresentationExamples
GEra designatorTextAD
yYearYear1996; 96
YWeek yearYear2009; 09
MMonth in year (context sensitive)MonthJuly; Jul; 07
LMonth in year (standalone form)MonthJuly; Jul; 07
wWeek in yearNumber27
WWeek in monthNumber2
DDay in yearNumber189
dDay in monthNumber10
FDay of week in monthNumber2
EDay name in weekTextTuesday; Tue
uDay number of week (1 = Monday, ..., 7 = Sunday)Number1
aAm/pm markerTextPM
HHour in day (0-23)Number0
kHour in day (1-24)Number24
KHour in am/pm (0-11)Number0
hHour in am/pm (1-12)Number12
mMinute in hourNumber30
sSecond in minuteNumber55
SMillisecondNumber978
zTime zoneGeneral time zonePacific Standard Time; PST; GMT-08:00
ZTime zoneRFC 822 time zone-0800
XTime zoneISO 8601 time zone-08; -0800; -08:00

Examples

Date and Time PatternResult
"yyyy.MM.dd G 'at' HH:mm:ss z"2001.07.04 AD at 12:08:56 PDT
"EEE, MMM d, ''yy"Wed, Jul 4, '01
"h:mm a"12:08 PM
"hh 'o''clock' a, zzzz"12 o'clock PM, Pacific Daylight Time
"K:mm a, z"0:08 PM, PDT
"yyyyy.MMMMM.dd GGG hh:mm aaa"02001.July.04 AD 12:08 PM
"EEE, d MMM yyyy HH:mm:ss Z"Wed, 4 Jul 2001 12:08:56 -0700
"yyMMddHHmmssZ"010704120856-0700
"yyyy-MM-dd'T'HH:mm:ss.SSSZ"2001-07-04T12:08:56.235-0700
"yyyy-MM-dd'T'HH:mm:ss.SSSXXX"2001-07-04T12:08:56.235-07:00
"YYYY-'W'ww-u"2001-W27-3