Java TimeZone 和 Linux TimeZone 夏令时不匹配
Java TimeZone and Linux TimeZone Daylight Savings doesnot match
我需要创建 POSIX TimeZone 格式,定义如下。
std offset dst [offset],start[/time],end[/time]
例如 "America/New_York" 的 POSIX 格式是
EST+5EDT,M3.2.0/2,M11.1.0/2
现在值 M3.2.0/2 以 Mm.w.d/t.
的形式表示
这指定了第 m 个月第 w 周的第 d 天。日期 d 必须介于 0(星期日)和 6 之间。星期 w 必须介于 1 和 5 之间;第 1 周是第 d 天出现的第一周,第 5 周指定该月的最后 d 天。月份m应该在1到12之间,上面的解释借用了下面link
http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
所以上面的例子说明,UTC 的正常偏差是 5 小时;因为这是本初子午线以西,所以符号是正的。夏令时从 3 月的第二个星期日 2:00am 开始,到 11 月的第一个星期日 2:00am 结束。
当我在 Linux 时区文件 /usr/share/zoneinfo/America/New_York 中检查它时,它与上面的值匹配
EST5EDT,M3.2.0,M11.1.0
然而,当我在 java 中为时区 "America/New_York" 构造它时,我得到以下字符串
EST-5EDT+1,M2.1.1/2,M10.1.1/2
我通过从以下代码的输出中提取信息来构造上述字符串。
TimeZone timezone = TimeZone.getTimeZone("America/New_York");
System.out.println(timezone.toString());
输出结果如下
sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]
注意值 endMonth=10 与 Linux 输出相比应该是 11。
不建议依赖 toString
的输出,因为在 TimeZone 或 SimpleTimeZone 类.
中没有关于其格式的合同保证
很明显,你的月份数字差了一个;一个月中的第几周并不那么简单,因为您需要考虑该月的第一个完整周。
我会使用 Java 记录的 public 方法来获取信息:
static String posixSpecFor(TimeZone tz) {
Formatter posixSpec = new Formatter();
float offset = (float) tz.getRawOffset() / (1000 * 60 * 60) * -1;
posixSpec.format("%s%s%s",
tz.getDisplayName(false, TimeZone.SHORT),
offset >= 0 ? "+" : "",
new DecimalFormat("0.##").format(offset));
if (tz.observesDaylightTime()) {
posixSpec.format("%s", tz.getDisplayName(true, TimeZone.SHORT));
}
ZoneId zone = tz.toZoneId();
TemporalField weekOfMonth =
WeekFields.of(DayOfWeek.SUNDAY, 7).weekOfMonth();
int thisYear = Year.now(zone).getValue();
List<ZoneOffsetTransitionRule> rules =
zone.getRules().getTransitionRules();
if (rules.size() > 2) {
rules = rules.subList(0, 2);
}
for (ZoneOffsetTransitionRule rule : rules) {
posixSpec.format(",M%d.%d.%d/%s",
rule.getMonth().getValue(),
rule.createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rule.getDayOfWeek().getValue() % 7,
rule.getLocalTime());
}
return posixSpec.toString();
}
以下是我用来构造Posix时区字符串
的完整代码
public class Posix时区 {
public String toPosixTZ(String timezoneStr) {
TimeZone timezone = TimeZone.getTimeZone(timezoneStr);
sop("timezoneStr", timezoneStr);
String posixTX = "";
PosixTimeZoneData pTZData = new PosixTimeZoneData(timezone);
if (timezone.useDaylightTime()) {
posixTX = getPosixDSString(pTZData);
} else {
posixTX = getPosixString(pTZData);
}
return posixTX;
}
public static void main(String args[]) {
System.out.println("Posix TimeZone is " + new PosixTimeZone().toPosixTZ(args[0]));
}
private void sop(String varname, String meesage) {
System.out.println("**************: " + varname + " = " + meesage);
}
private String getPosixDSString(PosixTimeZoneData pTZData) {
String posixString = "";
if ((pTZData.std != null && !pTZData.std.isEmpty())
&& (pTZData.stdOffset != null)//&& !pTZData.stdOffset.isEmpty())
&& (pTZData.dst != null && !pTZData.dst.isEmpty())
&& (pTZData.dstOffset != null)// && !pTZData.dstOffset.isEmpty())
&& (pTZData.start != null && !pTZData.start.isEmpty())
&& (pTZData.end != null && !pTZData.end.isEmpty())) {
posixString = String.format("%s%s%s%s,%s,%s", pTZData.std, pTZData.stdOffset, pTZData.dst,
pTZData.dstOffset, pTZData.start, pTZData.end);
} else {
sop("Error", "Invalid Parameters");
}
return posixString;
}
private String getPosixString(PosixTimeZoneData pTZData) {
String posixString = "";
if ((pTZData.std != null && !pTZData.std.isEmpty())
&& (pTZData.stdOffset != null && !pTZData.stdOffset.isEmpty())) {
posixString = String.format("%s%s", pTZData.std, pTZData.stdOffset);
} else {
sop("Error", "Invalid Parameters");
}
return posixString;
}
class PosixTimeZoneData {
String std = "";
String stdOffset = "";
String dst = "";
String dstOffset = "";
String start = "";
String end = "";
private PosixTimeZoneData(TimeZone timeZone) {
std = timeZone.getDisplayName(false, TimeZone.SHORT);
int rawOffset = (timeZone.getRawOffset() / 3600000) * -1;
stdOffset = (rawOffset >= 0)
? ((rawOffset == 0) || (rawOffset == 1) ? "" : "+" + rawOffset)
: "" + rawOffset;
if (timeZone.useDaylightTime()) {
dst = timeZone.getDisplayName(true, TimeZone.SHORT);
int dstRawOffset = timeZone.getDSTSavings() / 3600000;
dstOffset = (dstRawOffset >= 0)
? ((dstRawOffset == 0) || (dstRawOffset == 1) ? "" : "+" + dstRawOffset)
: "" + dstRawOffset;
ZoneId zone = timeZone.toZoneId();
TemporalField weekOfMonth
= WeekFields.of(DayOfWeek.SUNDAY, 7).weekOfMonth();
int thisYear = Year.now(zone).getValue();
List<ZoneOffsetTransitionRule> rules
= zone.getRules().getTransitionRules();
if (rules != null && !rules.isEmpty()) {
if (rules.size() > 2) {
rules = rules.subList(0, 2);
}
start = String.format("M%d.%d.%d/%s",
rules.get(0).getMonth().getValue(),
rules.get(0).createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rules.get(0).getDayOfWeek().getValue() % 7,
rules.get(0).getLocalTime().getHour());
end = String.format("M%d.%d.%d/%s",
rules.get(1).getMonth().getValue(),
rules.get(1).createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rules.get(1).getDayOfWeek().getValue() % 7,
rules.get(1).getLocalTime().getHour());
}
}
}
}
}
我需要创建 POSIX TimeZone 格式,定义如下。
std offset dst [offset],start[/time],end[/time]
例如 "America/New_York" 的 POSIX 格式是 EST+5EDT,M3.2.0/2,M11.1.0/2
现在值 M3.2.0/2 以 Mm.w.d/t.
的形式表示这指定了第 m 个月第 w 周的第 d 天。日期 d 必须介于 0(星期日)和 6 之间。星期 w 必须介于 1 和 5 之间;第 1 周是第 d 天出现的第一周,第 5 周指定该月的最后 d 天。月份m应该在1到12之间,上面的解释借用了下面link
http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
所以上面的例子说明,UTC 的正常偏差是 5 小时;因为这是本初子午线以西,所以符号是正的。夏令时从 3 月的第二个星期日 2:00am 开始,到 11 月的第一个星期日 2:00am 结束。
当我在 Linux 时区文件 /usr/share/zoneinfo/America/New_York 中检查它时,它与上面的值匹配
EST5EDT,M3.2.0,M11.1.0
然而,当我在 java 中为时区 "America/New_York" 构造它时,我得到以下字符串
EST-5EDT+1,M2.1.1/2,M10.1.1/2
我通过从以下代码的输出中提取信息来构造上述字符串。
TimeZone timezone = TimeZone.getTimeZone("America/New_York");
System.out.println(timezone.toString());
输出结果如下
sun.util.calendar.ZoneInfo[id="America/New_York",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=America/New_York,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]
注意值 endMonth=10 与 Linux 输出相比应该是 11。
不建议依赖 toString
的输出,因为在 TimeZone 或 SimpleTimeZone 类.
很明显,你的月份数字差了一个;一个月中的第几周并不那么简单,因为您需要考虑该月的第一个完整周。
我会使用 Java 记录的 public 方法来获取信息:
static String posixSpecFor(TimeZone tz) {
Formatter posixSpec = new Formatter();
float offset = (float) tz.getRawOffset() / (1000 * 60 * 60) * -1;
posixSpec.format("%s%s%s",
tz.getDisplayName(false, TimeZone.SHORT),
offset >= 0 ? "+" : "",
new DecimalFormat("0.##").format(offset));
if (tz.observesDaylightTime()) {
posixSpec.format("%s", tz.getDisplayName(true, TimeZone.SHORT));
}
ZoneId zone = tz.toZoneId();
TemporalField weekOfMonth =
WeekFields.of(DayOfWeek.SUNDAY, 7).weekOfMonth();
int thisYear = Year.now(zone).getValue();
List<ZoneOffsetTransitionRule> rules =
zone.getRules().getTransitionRules();
if (rules.size() > 2) {
rules = rules.subList(0, 2);
}
for (ZoneOffsetTransitionRule rule : rules) {
posixSpec.format(",M%d.%d.%d/%s",
rule.getMonth().getValue(),
rule.createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rule.getDayOfWeek().getValue() % 7,
rule.getLocalTime());
}
return posixSpec.toString();
}
以下是我用来构造Posix时区字符串
的完整代码public class Posix时区 {
public String toPosixTZ(String timezoneStr) {
TimeZone timezone = TimeZone.getTimeZone(timezoneStr);
sop("timezoneStr", timezoneStr);
String posixTX = "";
PosixTimeZoneData pTZData = new PosixTimeZoneData(timezone);
if (timezone.useDaylightTime()) {
posixTX = getPosixDSString(pTZData);
} else {
posixTX = getPosixString(pTZData);
}
return posixTX;
}
public static void main(String args[]) {
System.out.println("Posix TimeZone is " + new PosixTimeZone().toPosixTZ(args[0]));
}
private void sop(String varname, String meesage) {
System.out.println("**************: " + varname + " = " + meesage);
}
private String getPosixDSString(PosixTimeZoneData pTZData) {
String posixString = "";
if ((pTZData.std != null && !pTZData.std.isEmpty())
&& (pTZData.stdOffset != null)//&& !pTZData.stdOffset.isEmpty())
&& (pTZData.dst != null && !pTZData.dst.isEmpty())
&& (pTZData.dstOffset != null)// && !pTZData.dstOffset.isEmpty())
&& (pTZData.start != null && !pTZData.start.isEmpty())
&& (pTZData.end != null && !pTZData.end.isEmpty())) {
posixString = String.format("%s%s%s%s,%s,%s", pTZData.std, pTZData.stdOffset, pTZData.dst,
pTZData.dstOffset, pTZData.start, pTZData.end);
} else {
sop("Error", "Invalid Parameters");
}
return posixString;
}
private String getPosixString(PosixTimeZoneData pTZData) {
String posixString = "";
if ((pTZData.std != null && !pTZData.std.isEmpty())
&& (pTZData.stdOffset != null && !pTZData.stdOffset.isEmpty())) {
posixString = String.format("%s%s", pTZData.std, pTZData.stdOffset);
} else {
sop("Error", "Invalid Parameters");
}
return posixString;
}
class PosixTimeZoneData {
String std = "";
String stdOffset = "";
String dst = "";
String dstOffset = "";
String start = "";
String end = "";
private PosixTimeZoneData(TimeZone timeZone) {
std = timeZone.getDisplayName(false, TimeZone.SHORT);
int rawOffset = (timeZone.getRawOffset() / 3600000) * -1;
stdOffset = (rawOffset >= 0)
? ((rawOffset == 0) || (rawOffset == 1) ? "" : "+" + rawOffset)
: "" + rawOffset;
if (timeZone.useDaylightTime()) {
dst = timeZone.getDisplayName(true, TimeZone.SHORT);
int dstRawOffset = timeZone.getDSTSavings() / 3600000;
dstOffset = (dstRawOffset >= 0)
? ((dstRawOffset == 0) || (dstRawOffset == 1) ? "" : "+" + dstRawOffset)
: "" + dstRawOffset;
ZoneId zone = timeZone.toZoneId();
TemporalField weekOfMonth
= WeekFields.of(DayOfWeek.SUNDAY, 7).weekOfMonth();
int thisYear = Year.now(zone).getValue();
List<ZoneOffsetTransitionRule> rules
= zone.getRules().getTransitionRules();
if (rules != null && !rules.isEmpty()) {
if (rules.size() > 2) {
rules = rules.subList(0, 2);
}
start = String.format("M%d.%d.%d/%s",
rules.get(0).getMonth().getValue(),
rules.get(0).createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rules.get(0).getDayOfWeek().getValue() % 7,
rules.get(0).getLocalTime().getHour());
end = String.format("M%d.%d.%d/%s",
rules.get(1).getMonth().getValue(),
rules.get(1).createTransition(thisYear).getDateTimeBefore().get(
weekOfMonth),
rules.get(1).getDayOfWeek().getValue() % 7,
rules.get(1).getLocalTime().getHour());
}
}
}
}
}