Ruby里Time#to_json的两种格式

晚上在接入方的提醒下,注意到了一个之前没在意的问题:OPEN API中所有返回的json时间格式都是2017-08-25T21:30:28.388Z这样的。

“这个不好解析”,接入方如是说。

“大概是没法一行解析吧”,同事这么讲。

快醒醒,他们用的可是java,java怎么可能只写一行,分分钟给你写个大项目出来!

public static Date stringToDate(String dateStr) {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
        Date date = null;
        try {
            date = format.parse(dateStr);
        } catch (Exception e) {
            e.printStackTrace();
            Log.e(Constants.LOG_TAG_C, "String to date error : " + e, e);
        }
        return date;
    }

好吧,回归正题,我这才意识到为什么这个时间的格式是这样子的。

我尝试在repl查看结果:

2.4.1 :011 > PlayRecord.last.created_at.to_json
 => "\"2017-08-25T21:30:28.388Z\""
2.4.1 :012 > PlayRecord.last.created_at.as_json
 => "2017-08-25T21:30:28.388Z"
2.4.1 :013 > PlayRecord.last.created_at.to_s
 => "2017-08-25 21:30:28 UTC"
2.4.1 :014 > PlayRecord.last.created_at.class
 => Time
2.4.1 :015 > Time.now.to_s
 => "2017-08-25 21:39:50 +0800"
2.4.1 :016 > Time.now.to_json
 => "\"2017-08-25T21:39:56.335+08:00\""

Time对象调用to_json函数得到得到的结果不一样,调用to_s的结果也不一样,to_json底层甚至没有按照我设想的调用to_s方法。

第一个反应ActiveRecord以及ActiveSupport给Time类增加了内容,就在github的茫茫多源码里找,未果。

我要怎么知道具体的to_json函数究竟写了点什么呢?

Google “how to get source code ruby”

https://stackoverflow.com/questions/3393096/how-can-i-get-source-code-of-a-method-dynamically-and-also-which-file-is-this-me

通过 method方法拿到具体某个方法对象,调用source_location可以拿到原文件的位置。

想再偷懒一点还可以直接打印到屏幕上

require 'method_source'
Set.instance_method(:merge).source.display

有了这两个内省的方法,就可以很方便的找到最终的位置了

/active_support/core_ext/object/json.rb 这个文件给Time类加了as_json的函数

(实际上,grape api 在最后返回的时候并不是简单的调用JSON.dump(data),而是调用了to_json来拿到最后的json字符串,但是to_json实际调用的是ActiveSupport::JSON.encode(self, options),最后一路找下去实际上就是
stringify jsonify value.as_json(options.dup),本质还是在调用as_json):

class Time
  def as_json(options = nil) #:nodoc:
    if ActiveSupport::JSON::Encoding.use_standard_json_time_format
      xmlschema(ActiveSupport::JSON::Encoding.time_precision)
    else
      %(#{strftime("%Y/%m/%d %H:%M:%S")} #{formatted_offset(false)})
    end
  end
end

具体在Time的xmlschema函数里会根据是否是utc时间来给出不同的格式,这里会直接format,并不会调用to_s

def xmlschema(fraction_digits=0)
    fraction_digits = fraction_digits.to_i
    s = strftime("%FT%T")
    if fraction_digits > 0
      s << strftime(".%#{fraction_digits}N")
    end
    s << (utc? ? 'Z' : strftime("%:z"))
  end

到了这里就发现,分叉的地方其实是utc?这个函数,当我们继续使用source_location的时候得到了一个nil,但是调用却是没问题的。

2.4.1 :003 > PlayRecord.last.created_at.utc?
 => true
2.4.1 :004 > Time.now.utc?
 => false

答案终于揭晓了,从ActiveRecord从数据库读取到的时间戳是UTC的,而本地Time.now得到的时间是有时区的。s << (utc? ? 'Z' : strftime("%:z"))这一样代码决定了最后出来的格式。

自然顺着这个思路,我们去看to_s这个方法,同样也是找不到源代码,找不到源代码的原因是因为是由ruby解释器实现的,在ruby-doc.org上可以看到具体的源代码,同样根据是否是UTC时间做了判断:

static VALUE
time_to_s(VALUE time)
{
    struct time_object *tobj;

    GetTimeval(time, tobj);
    if (TIME_UTC_P(tobj))
        return strftimev("%Y-%m-%d %H:%M:%S UTC", time, rb_usascii_encoding());
    else
        return strftimev("%Y-%m-%d %H:%M:%S %z", time, rb_usascii_encoding());
}

那么问题来了,既然是本地,在jruby下会怎样呢。
我特意去找了jruby的源码(/core/src/main/java/org/jruby/RubyTime.java):

    private final static DateTimeFormatter TO_S_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss Z").withLocale(Locale.ENGLISH);
    private final static DateTimeFormatter TO_S_UTC_FORMATTER = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss 'UTC'").withLocale(Locale.ENGLISH);

    @Override
    @JRubyMethod(name = {"to_s", "inspect"})
    public IRubyObject to_s() {
        return inspectCommon(TO_S_FORMATTER, TO_S_UTC_FORMATTER);
    }
    private IRubyObject inspectCommon(DateTimeFormatter formatter, DateTimeFormatter utcFormatter) {
        DateTimeFormatter simpleDateFormat;
        if (dt.getZone() == DateTimeZone.UTC) {
            simpleDateFormat = utcFormatter;
        } else {
            simpleDateFormat = formatter;
        }

        String result = simpleDateFormat.print(dt);

        if (isTzRelative) {
            // display format needs to invert the UTC offset if this object was
            // created with a specific offset in the 7-arg form of #new
            DateTimeZone dtz = dt.getZone();
            int offset = dtz.toTimeZone().getOffset(dt.getMillis());
            DateTimeZone invertedDTZ = DateTimeZone.forOffsetMillis(offset);
            DateTime invertedDT = dt.withZone(invertedDTZ);
            result = simpleDateFormat.print(invertedDT);
        }

        return RubyString.newString(getRuntime(), result, USASCIIEncoding.INSTANCE);
    }

(嗯,11行的C代码对应30行Java,Java除了啰嗦以外真的没黑点)

一样的逻辑,根据是否为utc时间来决定最后显示UTC还是+08:00。这个应该是写入语言规范了。

所以,最后,搞了这么多,有什么用呢?

没什么用,因为这个问题并不需要解决,形如2017-08-25T21:30:28.388Z实际上是iso8601的标准,对于utc时间后面加大写字母Z,非utc时间加时区偏移量。

不过既然到了最后,还是传授点经验,辣鸡的移动版safari的Date.parse并不能解析这个时间,甚至它都不能解析这样的2017-08-25 21:30:28的时间,因为它要求日期的分割符是/,当然你可以说“我已经不手写Date.parse了!”。但是不妨碍一起鄙视移动版safari,顺带再黑一次java

function parseISO8601(iso_str){
  var s = '2011-06-21T14:27:28.593Z';
  var a = s.split(/[^0-9]/);
  return new Date (a[0],a[1]-1,a[2],a[3],a[4],a[5] );
}

唉,我对Java真是爱的深沉。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,423评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,147评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,019评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,443评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,535评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,798评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,941评论 3 407
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,704评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,152评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,494评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,629评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,295评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,901评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,978评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,333评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,499评论 2 348

推荐阅读更多精彩内容