【问题标题】:Parse and retrieve timezone offset from date-time从日期时间解析和检索时区偏移量
【发布时间】:2018-01-12 16:35:31
【问题描述】:

日期格式:“yyyy-MM-dd'T'HH:mm:ss.SSSZ

输入日期:“2017-09-18T03:08:20.888+0200

问题:我需要从输入字符串中检索时区偏移量并在该时区打印解析的日期。换句话说,我需要输出与输入相同。

SimpleDateFormat 成功解析输入日期并返回java.util.Date 对象。 As we know,日期没有时区字段。 SimpleDateFormat 将解析的日期转换为其时区,默认情况下为系统时区。当我打印这个日期时,它是在系统时区打印的。

简单演示

private static void runDemoTask() throws ParseException {
    final String dateTimeTimezoneFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ";
    final SimpleDateFormat inputSdf = new SimpleDateFormat(dateTimeTimezoneFormat);
    final String inputDate = "2017-09-18T01:08:20.888+0200";

    Date parsedDate = inputSdf.parse(inputDate);

    final SimpleDateFormat outputSdf = new SimpleDateFormat(dateTimeTimezoneFormat);
    //outputSdf.setTimeZone("X_TIMEZONE_WHICH_I_NEED");
    String output = outputSdf.format(parsedDate);
    System.out.println(output);
}

输出

Mon Sep 18 00:08:20 GMT+01:00 2017

注意,输出日期有系统时区,与输入字符串不同。

注意,我不会使用java.timeJoda Time等库,因为我需要支持现有的代码。


可能的不愉快解决方案

我尝试使用正则表达式来检索符号和偏移量。

private static  String parseTimeZone(String input) {
    final int singGroup = 1;
    final int offsetGroup = 2;
    final String timezonePatternStr = "([+-])(\\d{4})$";
    final Pattern timezonePattern = Pattern.compile(timezonePatternStr);

    Matcher matcher = timezonePattern.matcher(input);
    if (matcher.find()) {
        String sign = matcher.group(singGroup);
        String offset = matcher.group(offsetGroup);
        System.out.println(sign + " " + offset);
    }

    return "";
} 

打印出来

+ 0200

【问题讨论】:

  • 好吧,您可以通过正则表达式提取时区部分,但您真的需要它吗?毕竟,无论在哪个时区,日期都是同一个时间点,这就是为什么它没有时间点。在向用户显示该时区时,您可以使用他们的(首选)时区,这可能只是另一个时区。
  • 吹毛求疵,+0200 不是时区,它是与 UTC 的偏移量。我想这就是你需要的?使用 java.time,现代 Java 日期和时间 API:OffsetDateTime.parse("2017-09-18T03:08:20.888+0200").getOffset()
  • 正则表达式的旁注:([+]|[-]) 可以简化为([+-]),因为[...] 是一个匹配任何一个 字符的字符类。如果减号不是第一个或最后一个字符,则需要对其进行转义,否则它将被解释为定义一个范围,如a-z
  • 等等。您的“现有代码”在哪个版本上运行?如果您可以使用 Java 8,您的工作会简单得多。
  • Java 8。可能,我会在那个代码位置使用 java.time 库...

标签: java date datetime


【解决方案1】:

谢谢你们,伙计们:@Thomas,@ole-v-v

final DateTimeFormatter inputSdf1 = DateTimeFormatter.ofPattern(dateTimeTimezoneFormat);
OffsetDateTime d = OffsetDateTime.parse(inputDate, inputSdf1);

ZoneOffset zo = d.getOffset();  //Offset from the input.
TimeZone tz = TimeZone.getTimeZone(zo.normalized());

outputSdf.setTimeZone(tz);
System.out.println(outputSdf.format(parsedDate));

【讨论】:

  • 很高兴您找到了可行的解决方案。通常我会说,既然您可以使用现代日期和时间 API java.time,那么全力以赴并完全跳过过时的类 TimeZoneDateSimpleDateFormat。如果您需要将其中一种类型的对象传递给您无法更改的遗留方法,那么您当然需要进行转换。我建议您在调用旧方法之前的最后一刻这样做,以尽可能使用现代 API。
【解决方案2】:

tl;博士

TimeZone.getTimeZone(       // Convert from modern java.time type (`ZoneOffset`/`ZoneId`) to legacy type (`TimeZone`)
    OffsetDateTime.parse (  // Instantiate a `OffsetDateTime`, a moment on the timeline.
        "2017-09-18T03:08:20.888+0200" ,
        DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" )
    ).getOffset()           // Extract a `ZoneOffset`, which is a subclass of `ZoneId`.
)

直接从现代ZoneOffset 转换为传统TimeZone

这里看到的代码类似于 Yan Khonski 的 Answers,但使用了 TimeZone.getTimeZone 的变体,它直接从现代 java.time 类(ZoneOffsetZoneID)转换为旧的 TimeZone 类。

虽然最终结果没有区别,但此方法使用显式转换方法。这是添加到旧日期时间类的许多新方法之一,用于与 java.time 对象进行转换。

使用这种转换方法可以让您的代码更加自文档化。也让您更清楚地意识到您正在有意识地在现代和传统课程之间移动。

String input = "2017-09-18T03:08:20.888+0200";
DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu-MM-dd'T'HH:mm:ss.SSSX" );

OffsetDateTime odt = OffsetDateTime.parse( input , f );  // Parse input.
ZoneOffset offset = odt.getOffset( );                    // Interrogate for the `ZoneOffset` object representing this moment’s offset-from-UTC (a number of hours/minutes/seconds).

TimeZone tz = TimeZone.getTimeZone( offset );            // Convert from modern java.time object (a `ZoneOffset`/`ZoneId`) to the legacy class `TimeZone`.

转储到控制台。

System.out.println( "odt.toString(): " + odt );
System.out.println( "offset.toString(): " + offset );
System.out.println( "tz.toString(): " + tz );

odt.toString(): 2017-09-18T03:08:20.888+02:00

offset.toString(): +02:00

tz.toString(): sun.util.calendar.ZoneInfo[id="GMT+02:00",offset=7200000,dstSavings=0,useDaylight=false,transitions=0,lastRule=null]


关于java.time

java.time 框架内置于 Java 8 及更高版本中。这些类取代了麻烦的旧 legacy 日期时间类,例如 java.util.DateCalendarSimpleDateFormat

Joda-Time 项目现在位于maintenance mode,建议迁移到java.time 类。

要了解更多信息,请参阅Oracle Tutorial。并在 Stack Overflow 上搜索许多示例和解释。规格为JSR 310

从哪里获得 java.time 类?

ThreeTen-Extra 项目通过附加类扩展了 java.time。该项目是未来可能添加到 java.time 的试验场。您可以在这里找到一些有用的类,例如IntervalYearWeekYearQuartermore

【讨论】:

    【解决方案3】:

    SimpleDateFormat 扩展了DateFormat,因此在内部使用Calendar。在解析日历更新的日期时,您可以在 解析后从中获取时区:

    罢工>

    //use the timezone of the internally stored calendar
    outputSdf.setTimeZone( inputSdf.getTimezone() );
    

    这也说明了为什么DateFormat 不是线程安全的。

    编辑:

    内部日历的时区似乎未更新,但 ZONE_OFFSET 字段已更新。因此你可以这样做:

    int zoneOffset = inputSdf.getCalendar().get( Calendar.ZONE_OFFSET );
    //length check etc. left for you
    String matchingZoneId = TimeZone.getAvailableIDs( zoneOffset )[0];
    outputSdf.setTimeZone( TimeZone.getTimeZone( matchingZoneId ) );
    

    请注意,您不能只设置输出格式的区域偏移量,因为这不会更新格式化时使用的时区参考。

    正如您所见,这样做看起来有点“笨拙”,因此您应该认真考虑您是否真的需要时区。在大多数情况下,无论如何您都会以不同的方式定义输出时区,例如通过获取用户的位置、输入等。

    【讨论】:

    • 这对我不起作用。 inputSdf.getTimezone() 返回我的系统时区 -0600 而不是 +0200
    • @tsolakp 你是对的,因为我在 OP 的时区我被骗了。将再次检查。
    • @YanKhonski 你是在 解析之后调用的吗?当我在解析之前调用它时,我也会得到 3600000,因为初始时区将是系统时区,即 +0100。解析后我得到 7200000。
    【解决方案4】:

    硬核解决方案!

    解析时区并检索小时和分钟并签名并构建时区!

    public class ParseTimeZone {
    
        private static final String TIME_ZONE_REGEX = "([+-])(\\d{2})[:]?(\\d{2})$";
        private static final Pattern TIME_ZONE_PATTERN = Pattern.compile(TIME_ZONE_REGEX);
    
        private static final int SING_GROUP = 1;
        private static final int HOURS_GROUP = 2;
        private static final int MINUTES_GROUP = 3;
    
        private static final String PLUS = "+";
        private static final String MINUS = "-";
    
        private static final int MINUTES_IN_HOUR = 60;
        private static final int SECONDS_IN_MINUTE = 60;
    
        public static void main(String[] args) {
            final String inputDate = "2017-09-18T01:08:20.888Z";
            parseTimeZone(inputDate);
        }
    
        private static String parseTimeZone(String input) {
            input = fixDateStringWithZeroZoneOffset(input);
            Matcher matcher = TIME_ZONE_PATTERN.matcher(input);
            if (!matcher.find()) {
                return "";
            }
    
            String sign = matcher.group(SING_GROUP);
            String hours = matcher.group(HOURS_GROUP);
            String minutes = matcher.group(MINUTES_GROUP);
    
            int offsetSeconds = calculateOffsetSeconds(sign, hours, minutes);
    
            ZoneOffset zoneOffset = ZoneOffset.ofTotalSeconds(offsetSeconds);
            System.out.println(zoneOffset);
    
            TimeZone timeZone = TimeZone.getTimeZone(zoneOffset);
            System.out.println(timeZone);
    
            return "";
        }
    
        private static int calculateOffsetSeconds(String signStr, String hoursStr, String minutesStr) {
            try {
                int hours = Integer.parseInt(hoursStr);
                int minutes = Integer.parseInt(minutesStr);
                int sign = parseSign(signStr);
    
                int seconds = sign * ((hours * MINUTES_IN_HOUR + minutes) * SECONDS_IN_MINUTE);
                return seconds;
    
            } catch (NumberFormatException e) {
                throw new RuntimeException("It should not happen because it matches the regular expression. There should be numbers.", e);
            }
        }
    
        private static int parseSign(String sign) {
            if (sign.equals(PLUS)) {
                return 1;
            } else if (sign.equals(MINUS)) {
                return -1;
            } else {
                throw new RuntimeException("Offset sign should be + or -.");
            }
        }
    
        private static String fixDateStringWithZeroZoneOffset(String dateString) {
            if (dateString.endsWith("Z")) {
                return dateString.replaceAll("Z$", "+0000");
            }
            return dateString;
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-02-14
      • 2016-10-29
      • 1970-01-01
      • 2018-03-15
      • 2016-11-10
      • 2021-12-19
      • 1970-01-01
      • 2014-02-04
      相关资源
      最近更新 更多