从日期时间

时间:2018-01-12 16:35:31

标签: java date datetime

日期格式:" yyyy-MM-dd' H&H: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.time Joda 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

4 个答案:

答案 0 :(得分:2)

谢谢你,伙计们:@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));

答案 1 :(得分:1)

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 ) );

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

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

答案 2 :(得分:1)

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类(ZoneOffset&amp; ZoneID转换为遗留的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 =&#34; GMT + 02:00&#34;,offset = 7200000,dstSavings = 0,useDaylight = false,transitions = 0,lastRule =空]

关于java.time

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

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

要了解详情,请参阅Oracle Tutorial。并搜索Stack Overflow以获取许多示例和解释。规范是JSR 310

从哪里获取java.time类?

ThreeTen-Extra项目使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在此处找到一些有用的课程,例如IntervalYearWeekYearQuartermore

答案 3 :(得分:0)

Hardcore解决方案!

解析时区并检索小时和分钟,然后签名并建立时区!

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;
    }
}