美文网首页
为什么format(System.currentTimeMill

为什么format(System.currentTimeMill

作者: 沉默的小象 | 来源:发表于2023-06-28 18:05 被阅读0次

    System.currentTimeMillis() 获取的是从伦敦时间1970年1月1日 00:00:00 到现在的总时间,单位是毫秒。今年是2023年,那么这段时间转为年,应该是53左右。用代码验证下:

    public class CurrentMills {
        public static void main(String[] args) {
    
            long total = System.currentTimeMillis();
            //估算,不考虑闰年。
            //注意365 * 24 * 60 * 60 * 1000已经超过int最大值了,所以用365L转为long
            float year_num = total * 1.0f / (365L * 24 * 60 * 60 * 1000);
            System.out.println(year_num);
        }
    }
    

    输出是:

    53.52703
    

    通过上面的测试,基本可以确认是从伦敦时间1970年1月1日 00:00:00 到现在的总时间。
    那么按常理,格式化之后,应该显示的是伦敦时间,比北京时间早8小时。测试下:

    public class CurrentMills {
        public static void main(String[] args) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String dateStr = dateFormat.format(System.currentTimeMillis());
            System.out.println(dateStr);
        }
    }
    

    看下输出结果:


    image.png

    再看看电脑当前的北京时间:


    image.png

    伦敦时间应该比现在早8小时,所以应该输出的是早上6点。这里为什么输出的是14点?

    我们设置时区试下:

    import java.util.TimeZone;
    
    public class CurrentMills {
        public static void main(String[] args) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); //在这里设置时区
            String dateStr = dateFormat.format(System.currentTimeMillis());
            System.out.println(dateStr);
        }
    }
    

    看下输出时间:


    image.png

    果然是早上6点了。看来是和时区设置有关。

    SimpleDateFormat的默认时区是哪个?

    打印出来看看:

    public class CurrentMills {
        public static void main(String[] args) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            System.out.println(dateFormat.getTimeZone().getID());
            System.out.println(dateFormat.getTimeZone().getDisplayName());
        }
    }
    

    输出:

    Asia/Shanghai
    中国标准时间
    

    看到输出Shanghai,惊讶了吧,北京和上海都是属于东八区,我们现在都说北京时间,那为什么显示的是Shanghai 而不是Beijing了?这篇文章timeZone为什么是Asia/Shanghai,而不是Asia/Beijing做了解答,简单说: 当年在分配时区(timeZone)的时候,还是在1949年之前,那时候,上海的洋人比较多,北京地位还不如上海。

    我们来分析下,这个时区到底在什么时候设置进去的?

    先看format方法,看看里面有没有设置时区。

     //Format.java
    
        public final String format (Object obj) {
            return format(obj, new StringBuffer(), new FieldPosition(0)).toString();
        }
        
        //抽象方法,在子类DateFormat中实现
        public abstract StringBuffer format(Object obj,
                        StringBuffer toAppendTo,
                        FieldPosition pos); 
    
    //DateFormat.java 
        public final StringBuffer format(Object obj, StringBuffer toAppendTo,
                                         FieldPosition fieldPosition)
        {
            if (obj instanceof Date)
                return format( (Date)obj, toAppendTo, fieldPosition );
            else if (obj instanceof Number) //代码走了这个else if
                return format( new Date(((Number)obj).longValue()),
                              toAppendTo, fieldPosition );
            else
                throw new IllegalArgumentException("Cannot format given Object as a Date");
        }
    

    执行了new Date(long),我们看看Date的构造器:

        public Date(long date) {
            fastTime = date;
        }
    

    Date的构造器并没有做是什么,看来时区不是在Date的构造器里面设置的。继续看DateFormat.java

        public abstract StringBuffer format(Date date, StringBuffer toAppendTo,
                                            FieldPosition fieldPosition);
    

    抽象方法,看子类SimpleDateFormat.java的实现:

        public StringBuffer format(Date date, StringBuffer toAppendTo,
                                   FieldPosition pos)
        {
            pos.beginIndex = pos.endIndex = 0;
            return format(date, toAppendTo, pos.getFieldDelegate());
        }
    
        private StringBuffer format(Date date, StringBuffer toAppendTo,
                                    FieldDelegate delegate) {
            // Convert input date to time field list
            calendar.setTime(date);  //这里有个calendar实例,也就是说SimpleDateFormat是持有Calendar对象的。
    
            boolean useDateFormatSymbols = useDateFormatSymbols();
    
            for (int i = 0; i < compiledPattern.length; ) {
                int tag = compiledPattern[i] >>> 8;
                int count = compiledPattern[i++] & 0xff;
                if (count == 255) {
                    count = compiledPattern[i++] << 16;
                    count |= compiledPattern[i++];
                }
    
                switch (tag) {
                case TAG_QUOTE_ASCII_CHAR:
                    toAppendTo.append((char)count);
                    break;
    
                case TAG_QUOTE_CHARS:
                    toAppendTo.append(compiledPattern, i, count);
                    i += count;
                    break;
    
                default:
                    subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);
                    break;
                }
            }
            return toAppendTo;
        }
    

    接着看Calendar.java的setTime()方法:

        public final void setTime(Date date) {
            setTimeInMillis(date.getTime());
        }
    
        public void setTimeInMillis(long millis) {
            // If we don't need to recalculate the calendar field values,
            // do nothing.
            if (time == millis && isTimeSet && areFieldsSet && areAllFieldsSet
                && (zone instanceof ZoneInfo) && !((ZoneInfo)zone).isDirty()) {  //这里有个zone,八成是时区
                return;
            }
            time = millis;
            isTimeSet = true;
            areFieldsSet = false;
            computeFields();
            areAllFieldsSet = areFieldsSet = true;
        }
    

    看看这个zone的定义:

        //The TimeZone used by this calendar. Calendar uses the time zone data to translate between locale and GMT time.
    
        private TimeZone        zone;
    

    果然是时区。我们理一下,SimpleDateFormat持有Calendar对象,Calendar又持有TimeZone对象,所以SimpleDateFormat初始化的时候,如果初始化了Calendar,那么TimeZone就会有默认值。我们看看SimpleDateFormat的构造器:

        public SimpleDateFormat(String pattern)
        {
            this(pattern, Locale.getDefault(Locale.Category.FORMAT));
        }
    
        public SimpleDateFormat(String pattern, Locale locale)
        {
            if (pattern == null || locale == null) {
                throw new NullPointerException();
            }
    
            initializeCalendar(locale); //找到目标了
            this.pattern = pattern;
            this.formatData = DateFormatSymbols.getInstanceRef(locale);
            this.locale = locale;
            initialize(locale);
        }
    
        private void initializeCalendar(Locale loc) {
            if (calendar == null) {
                assert loc != null;
                // The format object must be constructed using the symbols for this zone.
                // However, the calendar should use the current default TimeZone.
                // If this is not contained in the locale zone strings, then the zone
                // will be formatted using generic GMT+/-H:MM nomenclature.
                calendar = Calendar.getInstance(TimeZone.getDefault(), loc);
            }
        }
    

    果然,初始化了calendar,并且是单例的。接着看看TimeZone的getDefault()方法:

        public static TimeZone getDefault() {
            return (TimeZone) getDefaultRef().clone();
        }
    
        static TimeZone getDefaultRef() {
            TimeZone defaultZone = defaultTimeZone;
            if (defaultZone == null) {
                // Need to initialize the default time zone.
                defaultZone = setDefaultZone();
                assert defaultZone != null;
            }
            // Don't clone here.
            return defaultZone;
        }
    
        //defaultTimeZone的定义
        private static volatile TimeZone defaultTimeZone;
    
    

    defaultTimeZone的赋值有两个地方:

        private static synchronized TimeZone setDefaultZone() {
            TimeZone tz;
            // get the time zone ID from the system properties
            String zoneID = AccessController.doPrivileged(
                    new GetPropertyAction("user.timezone"));
    
            // if the time zone ID is not set (yet), perform the
            // platform to Java time zone ID mapping.
            if (zoneID == null || zoneID.isEmpty()) {
                String javaHome = AccessController.doPrivileged(
                        new GetPropertyAction("java.home"));
                try {
                    zoneID = getSystemTimeZoneID(javaHome);
                    if (zoneID == null) {
                        zoneID = GMT_ID;
                    }
                } catch (NullPointerException e) {
                    zoneID = GMT_ID;
                }
            }
    
            // Get the time zone for zoneID. But not fall back to
            // "GMT" here.
            tz = getTimeZone(zoneID, false);
    
            if (tz == null) {
                // If the given zone ID is unknown in Java, try to
                // get the GMT-offset-based time zone ID,
                // a.k.a. custom time zone ID (e.g., "GMT-08:00").
                String gmtOffsetID = getSystemGMTOffsetID();
                if (gmtOffsetID != null) {
                    zoneID = gmtOffsetID;
                }
                tz = getTimeZone(zoneID, true);
            }
            assert tz != null;
    
            final String id = zoneID;
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                @Override
                    public Void run() {
                        System.setProperty("user.timezone", id);
                        return null;
                    }
                });
    
            defaultTimeZone = tz;  //这里是赋值
            return tz;
        }
    
        public static void setDefault(TimeZone zone)
        {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) {
                sm.checkPermission(new PropertyPermission
                                   ("user.timezone", "write"));
            }
            defaultTimeZone = zone; //这里是赋值
        }
    

    这个defaultTimeZone和"user.timezone"有关。可以推理是获取本地属性。例如我在中国,就是中国标准时间。

    相关文章

      网友评论

          本文标题:为什么format(System.currentTimeMill

          本文链接:https://www.haomeiwen.com/subject/mwpoydtx.html