Java/Android - 将GMT时间字符串转换为本地时间

14

我有一个字符串,比如 "Tue May 21 14:32:00 GMT 2012",我想把它转换为本地时间,并使用格式“May 21, 2012 2:32 pm”。我尝试使用SimpleDateFormat("MM dd, yyyy hh:mm a").parse(),但是它抛出了一个异常。那么我该怎么办呢?

异常信息是 "unreported exception java.text.ParseException; must be caught or declared to be thrown."

异常发生在这行代码中:Date date = inputFormat.parse(inputText);

我在TextMate上运行的代码:

public class test{
    public static void main(String arg[]) {
        String inputText = "Tue May 22 14:52:00 GMT 2012";
        SimpleDateFormat inputFormat = new SimpleDateFormat(
            "EEE MMM dd HH:mm:ss 'GMT' yyyy", Locale.US);
        inputFormat.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));
        SimpleDateFormat out = new SimpleDateFormat("MMM dd, yyyy h:mm a");
        Date date = inputFormat.parse(inputText);
        String output = out.format(date);
       System.out.println(output);
    }
}

您上面的示例文本是21日,是星期一而不是星期二。与示例代码不同。 - Basil Bourque
5个回答

28

你提供的格式字符串用于解析,但实际得到的文本格式与之不符。你需要先进行解析,然后再进行格式化。看起来你想要的是:

SimpleDateFormat inputFormat = new SimpleDateFormat(
    "EEE MMM dd HH:mm:ss 'GMT' yyyy", Locale.US);
inputFormat.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));

SimpleDateFormat outputFormat = new SimpleDateFormat("MMM dd, yyyy h:mm a");
// Adjust locale and zone appropriately

Date date = inputFormat.parse(inputText);
String outputText = outputFormat.format(date);

编辑:以下是一个简短但完整的程序示例,包含您提供的样本输入:

import java.util.*;
import java.text.*;

public class Test {
    public static void main(String[] args) throws ParseException {
        String inputText = "Tue May 21 14:32:00 GMT 2012";
        SimpleDateFormat inputFormat = new SimpleDateFormat
            ("EEE MMM dd HH:mm:ss 'GMT' yyyy", Locale.US);
        inputFormat.setTimeZone(TimeZone.getTimeZone("Etc/UTC"));

        SimpleDateFormat outputFormat =
            new SimpleDateFormat("MMM dd, yyyy h:mm a");
        // Adjust locale and zone appropriately
        Date date = inputFormat.parse(inputText);
        String outputText = outputFormat.format(date);
        System.out.println(outputText);
    }
}

你能编译和运行那份完整的代码吗?


1
我尝试了,但是这行代码Date date = inputFormat.parse(inputText);仍然抛出了我上面提到的异常 :( - Manto
@user1066956:请提供涉及的确切字符串 - 因为我在问题中使用了您给我的字符串,它是正常的。 - Jon Skeet
“Tue May 21 14:32:00 GMT 2012” 是我使用的确切字符串。我在 TextMate 中运行了以下代码,并得到了上述提到的错误。 - Manto
@user1066956:我已经在答案中编辑了一个简短但完整的程序。你能在你的桌面上运行那段代码吗?当它失败时,是在Android还是Java中运行的? - Jon Skeet
顺便问一下,如果我们不知道时区(例如有时字符串带有America/Los_Angeles),我们应该用什么来替换“GMT”?我们应该放置zzzz吗? - Manto
显示剩余2条评论

3

您所使用的格式化程序必须定义为您期望的格式。以下是一个示例,适用于您提供的值,但是根据输入的一些边缘情况,您可能需要更改它:

String date = "Tue May 21 14:32:00 GMT 2012";
DateFormat inputFormat = new SimpleDateFormat("EE MMM dd HH:mm:ss zz yyy");
Date d = inputFormat.parse(date);
DateFormat outputFormat = new SimpleDateFormat("MMM dd, yyy h:mm a zz");
System.out.println(outputFormat.format(d));

1

SimpleDateFormat.parse方法会抛出一个解析异常。

你收到的异常信息是在告诉你...

异常信息是“未报告的异常java.text.ParseException; 必须被捕获或声明为抛出异常。

将执行解析的那一行代码用try-catch包裹起来,问题就解决了。

Date d=null;
try{
    d = inputFormat.parse(date);
catch(ParseException e){
   // handle the error here....
}

R


1
你正在使用麻烦的旧日期时间类,现在已被java.time类所取代。
错误的输入数据
你的第一个示例字符串是不正确的,因为21日是星期一而不是星期二。第二个示例字符串使用了22日,是正确的,并在我的示例代码中使用。
使用java.time 避免使用此类格式来表示日期时间值的文本表示形式。特别地,永远不要使用3-4个字母的缩写,例如EST或IST,在这种格式中看到,因为它们不是真正的时区,没有标准化,甚至不是唯一的(!)。指定一个适当的时区名称。在这种特殊情况下,java.time能够将其转换为GMT作为UTC,但其他值可能会失败。
String input = "Tue May 22 14:52:00 GMT 2012";
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "EEE MMM dd HH:mm:ss z uuuu" ).withLocale ( Locale.US );
ZonedDateTime zdt = ZonedDateTime.parse ( input , f );

System.out.println ( "zdt: " + zdt );

将内容转储到控制台。 toString 方法以标准ISO 8601格式生成字符串,并在括号中附加区域名称。当您需要将日期时间值序列化为文本时,这些标准格式是更好的选择。
System.out.println ( "zdt: " + zdt );

zdt: 2012-05-22T14:52Z[GMT]

生成字符串

您可以生成任何格式的字符串来表示此值。通常最好让java.time自动使用LocaleDateTimeFormatter进行本地化。

您需要的格式对于日期部分使用中等长度样式,但对于每日时间部分使用短长度样式。幸运的是,DateTimeFormatter允许您单独本地化每个部分,如在此处所示,我们传递一对FormatStyle对象。

Locale l = Locale.US;  // Or Locale.CANADA_FRENCH, or Locale.ITALY, etc.
DateTimeFormatter fOutput = DateTimeFormatter.ofLocalizedDateTime ( FormatStyle.MEDIUM , FormatStyle.SHORT ).withLocale ( l );
String output = zdt.format ( fOutput );

将数据输出到控制台。

System.out.println ( "zdt: " + zdt + " | output: " + output );

zdt: 2012-05-22T14:52Z[GMT] | output: May 22, 2012 2:52 PM

关于java.time

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

Joda-Time项目现在处于维护模式,建议迁移到java.time。

要了解更多信息,请参见Oracle教程。并在Stack Overflow上搜索许多示例和解释。

许多java.time的功能被移植到Java 6和7中ThreeTen-Backport,并在ThreeTenABP中进一步适应Android(请参见如何使用...)。内容保留html标签。

ThreeTen-Extra项目扩展了java.time的附加类。该项目是java.time可能未来添加的试验场。您可能会在这里找到一些有用的类,例如IntervalYearWeekYearQuarter等等。内容保留html标签。


0
const val DATE_HYPHEN_FORMAT = "yyyy-MM-dd"
const val DATE_MMM_DD_YYYY_FORMAT = "MMM dd, yyyy"
const val DATE_MMMM_DD_YYYY_FORMAT = "MMMM dd, yyyy"

const val FULL_DAY_NAME_FORMAT = "EEEE"
const val DATE_EEE_DD_MMMM_YYYY_FORMAT = "EEE dd, MMMM yyyy"
const val DATE_EEEE_DD_MMMM_YYYY_FORMAT = "EEEE dd, MMMM yyyy"

const val DATETIME_24_FORMAT = "dd-MM-yyyy'T'HH:mm:ss"

const val DATETIME_24_YMD_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"
const val DATE_WITH_MONTH_NAME_MMM_DY_FORMAT = DateFormat.MEDIUM
const val DATE_WITH_MONTH_FULL_NAME_MMMM_DY_FORMAT = DateFormat.LONG

const val TIME_24H_FORMAT = "HH:mm:ss"
const val TIME_FORMAT_AM_PM = "hh:mm aa"
const val TIME_24H_FORMATWithoutSS = "HH:mm"

enum class TimeZoneTo {
    NONE, UTC, LOCAL
}


fun changeFormat(
    dateTime: String,
    fromFormat: Int = DATE_WITH_MONTH_NAME_MMM_DY_FORMAT,
    toFormat: String = DATETIME_24_FORMAT,
    convertIn: TimeZoneTo = TimeZoneTo.NONE,
    needOnlyTime: Boolean = false
): String {
    try {
        val parser = DateFormat.getDateInstance(fromFormat)
        val finalDateTime = trimDateTime(dateTime)

        val date = parser.parse(finalDateTime)
        val sdf = SimpleDateFormat(toFormat)

        return format(
            date = date!!, formatter = sdf, convertIn = convertIn, needOnlyTime = needOnlyTime
        )
    } catch (e: AssertionError) {
        e.printStackTrace()
    } catch (e: Exception) {
        e.printStackTrace()
    }

    return dateTime // Return Same DateTime - only iff unable to change format
}



fun changeFormat(
    dateTime: String,
    fromFormat: String = DATETIME_24_FORMAT,
    toFormat: Int = DATE_WITH_MONTH_NAME_MMM_DY_FORMAT,
    convertIn: TimeZoneTo = TimeZoneTo.NONE,
    needOnlyTime: Boolean = false
): String {
    try {
        val sfdInput = SimpleDateFormat(fromFormat, Locale.ROOT)
        val finalDateTime = trimDateTime(dateTime)

        val date: Date = sfdInput.parse(finalDateTime)!!
        val outputFormatter = DateFormat.getDateInstance(toFormat)

        return format(
            date = date,
            formatter = outputFormatter,
            convertIn = convertIn,
            needOnlyTime = needOnlyTime
        )
    } catch (e: AssertionError) {
        e.printStackTrace()
    } catch (e: Exception) {
        e.printStackTrace()
    }

    return dateTime // Return Same DateTime - only iff unable to change format
}

fun changeFormat(
    dateTime: String,
    fromFormat: String = DATETIME_24_FORMAT,
    toFormat: String = DATE_HYPHEN_FORMAT,
    convertInTimeZone: TimeZoneTo = TimeZoneTo.NONE,
    needOnlyTime: Boolean = false
): String {
    try {
        val sfdInput = SimpleDateFormat(fromFormat, Locale.ROOT)

        val finalDateTime = trimDateTime(dateTime)

        val date: Date = sfdInput.parse(finalDateTime)!!
        val sdfOutput = SimpleDateFormat(toFormat)

        return format(
            date = date,
            formatter = sdfOutput,
            convertIn = convertInTimeZone,
            needOnlyTime = needOnlyTime
        )
    } catch (e: AssertionError) {
        e.printStackTrace()
    } catch (e: Exception) {
        e.printStackTrace()
    }

    return dateTime // Return Same DateTime - only iff unable to change format
}


// Format Given Date as per specified timeZone
private fun format(
    date: Date,
    formatter: DateFormat,
    convertIn: TimeZoneTo,
    needOnlyTime: Boolean
): String {
    return when (convertIn) {
        TimeZoneTo.LOCAL -> {
            val zone = TimeZone.getTimeZone(Calendar.getInstance().timeZone.id)
            val newDate = Date(date.time + zone.getOffset(date.time))
            formatter.timeZone = zone

            val result = formatter.format(newDate)
            if (needOnlyTime) return result.substringAfter('T')
            else result
        }
        TimeZoneTo.UTC -> {
            formatter.timeZone = TimeZone.getTimeZone("UTC")
            val result = formatter.format(date)

            if (needOnlyTime) return result.substringAfter('T')
            else result
        }
        else -> {
            val result = formatter.format(date)
            if (needOnlyTime) return result.substringAfter('T')
            else result
        }
    }
}

我创建了3个重载方法,分别是changeFormat(...),具有以下参数:

  1. dateTime(字符串 -> 日期或时间或日期时间)
  2. fromFormat(字符串/整数 -> dateTime的当前格式)- 可选(即如果未提供默认值将被使用)
  3. toFormat(字符串/整数 -> dateTime的新格式)- 可选(即如果未提供默认值将被使用)
  4. convertInTimeZone(枚举 -> 在指定的时区中进行转换)
  5. needOnlyTime(布尔值 -> 如果为true,则仅返回转换后的时间,否则返回转换后的dateTime

希望这能帮助未来的某个人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接