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);
}
}
看下输出结果:
![](https://img.haomeiwen.com/i7769688/e9318a6241314d40.png)
再看看电脑当前的北京时间:
![](https://img.haomeiwen.com/i7769688/8a24e8da7e93e5b9.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);
}
}
看下输出时间:
![](https://img.haomeiwen.com/i7769688/a58fbcfdb789d6d8.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"
有关。可以推理是获取本地属性。例如我在中国,就是中国标准时间。
网友评论