如何在安卓设备中将UTC时间戳转换为本地时间?

52

我需要将从服务器获取的UTC时间戳转换为本地设备时间。目前,我的时间相差5小时。例如,当我向服务器发布时,发布时间显示为5小时前而不是1秒钟前。我该如何解决这个问题?

以下是我的代码:

long timestamp = cursor.getLong(columnIndex);
            CharSequence relTime = DateUtils
                    .getRelativeTimeSpanString(timestamp * 1000
                            + TimeZone.getDefault().getRawOffset(),
                            System.currentTimeMillis(),
                            DateUtils.MINUTE_IN_MILLIS);
            ((TextView) view).setText(relTime);
11个回答

73

Java:

int offset = TimeZone.getDefault().getRawOffset() + TimeZone.getDefault().getDSTSavings();
long now = System.currentTimeMillis() - offset;

Kotlin:

val offset: Int = TimeZone.getDefault().rawOffset + TimeZone.getDefault().dstSavings
val now: Long = System.currentTimeMillis() - offset

1
正是我所寻找的。 - Anirudh Sharma
19
为了考虑夏令时的影响:int gmtOffset = TimeZone.getDefault().getRawOffset() + TimeZone.getDefault().getDSTSavings(); - Farshid T
1
这应该是答案。 - Alexandr
5
这里不应该是减号吗?long now = System.currentTimeMillis() - gmtOffset - vigilancer
4
抱歉,这个答案不正确。自纪元以来的毫秒数始终是自纪元以来的。调整它们以适应时区和/或夏令时(DST)是没有意义的。如果您的代码最终给出了正确的结果,它仍然是错误的,并且肯定会让未来的读者感到困惑。 - Ole V.V.
显示剩余2条评论

56

将格式为“2011-06-23T15:11:32”的日期字符串转换为我们的时区。

private String getDate(String ourDate)
{
    try
    {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        Date value = formatter.parse(ourDate);

        SimpleDateFormat dateFormatter = new SimpleDateFormat("MM-dd-yyyy HH:mm"); //this format changeable
        dateFormatter.setTimeZone(TimeZone.getDefault());
        ourDate = dateFormatter.format(value);

        //Log.d("ourDate", ourDate);
    }
    catch (Exception e)
    {
        ourDate = "00-00-0000 00:00";
    }
  return ourDate;
}

1
这是整个SO上最好的答案。谢谢! - Lampione
这个也考虑到了夏令时吗? - user4623471
这个很完美。但是假设我正在尝试将UTC时区的2018-04-27 12:48:00转换为IST时区。现在,在此处进行转换后,它返回的是2018-04-27上午06:18:00而不是下午06:18:00,该怎么办?如果在此处有任何UTC时间介于12:00:00到12:59:59之间,这将返回错误的结果。在其他情况下都很完美。 - Dharmik Mavani
@madhusudhan 如何将此内容转换为 2018-12-01T20:21:36.793194+05:30 - kartheeki j

45

你的示例代码看起来一切正常。顺便说一下,如果服务器时间戳是UTC(即它是一个纪元时间戳),那么您就不必应用当前时区偏移量。换句话说,如果服务器时间戳是UTC,则可以将服务器时间戳和系统时间(System.currentTimeMillis())之间的差异作为系统时间是UTC(纪元)而简单获取。

我会检查来自服务器的时间戳是否符合您的预期。如果来自服务器的时间戳无法转换为您预期的日期(在本地时区),则当前时间戳与系统时间之间的差异将不符合您的预期。

使用 Calendar 获取当前时区。使用当前时区初始化 SimpleDateFormatter;然后记录服务器时间戳并验证其是否为您所期望的日期:

Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone();

/* debug: is it local time? */
Log.d("Time zone: ", tz.getDisplayName());

/* date formatter in local timezone */
SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy HH:mm:ss");
sdf.setTimeZone(tz);

/* print your timestamp and double check it's the date you expect */
long timestamp = cursor.getLong(columnIndex);
String localTime = sdf.format(new Date(timestamp * 1000)); // I assume your timestamp is in seconds and you're converting to milliseconds?
Log.d("Time: ", localTime);

如果打印出来的服务器时间不是您期望的时间,那么您的服务器时间不是在UTC时间。

如果打印出来的服务器时间是您期望的日期,则您不需要对其应用原始偏移量。因此,您的代码将更简单(减去所有调试日志):

long timestamp = cursor.getLong(columnIndex);
Log.d("Server time: ", timestamp);

/* log the device timezone */
Calendar cal = Calendar.getInstance();
TimeZone tz = cal.getTimeZone();
Log.d("Time zone: ", tz.getDisplayName());

/* log the system time */
Log.d("System time: ", System.currentTimeMillis());

CharSequence relTime = DateUtils.getRelativeTimeSpanString(
    timestamp * 1000,
    System.currentTimeMillis(),
    DateUtils.MINUTE_IN_MILLIS);

((TextView) view).setText(relTime);

谢谢@pestrella,这给了我一个格式化的时间格式,但我想要像“2分钟前”,“1小时前”这样显示时间,使用Android Utils。有什么解决方法吗...谢谢 - cavallo
出于好奇,您正在使用哪个服务器时间戳进行工作?另外,如果将DateUtils.MINUTE_IN_MILLIS更改为0,则可以获得以秒为单位的经过时间(例如,2秒前)。您能否尝试使用0而不是MINUTE_IN_MILLIS进行另一项测试?记录服务器时间戳。您得到了什么时间戳?您的TextView显示了什么? - pestrella
在这种情况下,可能是以下原因之一:1. 您的设备位于GMT+4时区,或2. 服务器返回的UTC时间戳是未来的时间,或3. 您的服务器实际上没有返回UTC时间戳。为了帮助调试此问题,我会记录服务器时间戳(Long而不是String格式化日期),并记录System.currentTimeMillis()(再次是Long值)。我还将记录设备时区(cal.getTimeZone().getDisplayName)。我将更新上面的代码以包含所需的日志记录。 - pestrella
嗨,这里的“cursos”变量是什么? - Daniel Krzyczkowski
要显示刚刚、xx分钟前或xx天等的时间,请使用此链接:https://github.com/curioustechizen/android-ago。 - Kalu Khan Luhar
显示剩余5条评论

9

我使用 kotlin 中的 扩展函数 完成了这项任务。

fun String.toDate(dateFormat: String = "yyyy-MM-dd HH:mm:ss", timeZone: TimeZone = TimeZone.getTimeZone("UTC")): Date {
    val parser = SimpleDateFormat(dateFormat, Locale.getDefault())
    parser.timeZone = timeZone
    return parser.parse(this)
}

fun Date.formatTo(dateFormat: String, timeZone: TimeZone = TimeZone.getDefault()): String {
    val formatter = SimpleDateFormat(dateFormat, Locale.getDefault())
    formatter.timeZone = timeZone
    return formatter.format(this)
}

使用方法:

"2018-09-10 22:01:00".toDate().formatTo("dd MMM yyyy")

Output: "11 Sep 2018"

注意:

确保适当的验证。


3
我已经做了类似这样的事情,从UTC时间戳中获取本地设备时区的日期。
private long UTC_TIMEZONE=1470960000;
private String OUTPUT_DATE_FORMATE="dd-MM-yyyy - hh:mm a"

getDateFromUTCTimestamp(UTC_TIMEZONE,OUTPUT_DATE_FORMATE);

这里是函数。
 public String getDateFromUTCTimestamp(long mTimestamp, String mDateFormate) {
        String date = null;
        try {
            Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
            cal.setTimeInMillis(mTimestamp * 1000L);
            date = DateFormat.format(mDateFormate, cal.getTimeInMillis()).toString();

            SimpleDateFormat formatter = new SimpleDateFormat(mDateFormate);
            formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
            Date value = formatter.parse(date);

            SimpleDateFormat dateFormatter = new SimpleDateFormat(mDateFormate);
            dateFormatter.setTimeZone(TimeZone.getDefault());
            date = dateFormatter.format(value);
            return date;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return date;
    }

结果:

12-08-2016 - 04:30 PM 

希望这对其他人有用。

DateFormat.format是从哪个包中导入的?它是java.text.DateFormat吗? - K Pradeep Kumar Reddy

3

java.time

java.util日期时间API及其格式化APISimpleDateFormat已经过时且容易出错。建议完全停止使用它们并转换为现代日期时间API*

使用现代日期时间APIjava.time的解决方案:

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;

public class Main {
    public static void main(String[] args) {
        // A sample timestamp as Unix epoch (i.e. seconds from 01-01-1970T00:00:00 GMT)
        long epochSeconds = 1632131465L;

        // Note: Use Instant#ofEpochMilli in case you have timestamp in milliseconds
        Instant instant = Instant.ofEpochSecond(epochSeconds);
        System.out.println(instant);

        LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
        System.out.println(ldt);
    }
}

以我的时区 Europe/London 输出:

2021-09-20T09:51:05Z
2021-09-20T10:51:05

在线演示

Instant 表示时间线上的一个瞬时点,通常用 UTC 时间表示。输出中的 Z 是零时区偏移量的时区指示符。 它代表 Zulu,并指定了 Etc/UTC 时区(其时区偏移量为 +00:00 小时)。

教程:日期时间 中了解更多关于现代日期时间 API 的知识。


* 如果由于任何原因,您必须仍然使用 Java 6 或 Java 7,则可以使用 ThreeTen-Backport,该工具将大部分java.time功能回溯到Java 6和7。 如果您正在开发 Android 项目并且您的 Android API 级别仍不符合 Java 8,请查看通过脱糖使用的 Java 8+ API如何在 Android 项目中使用 ThreeTenABP


1
本地时间转换为协调世界时 (UTC)
DateTime dateTimeNew = new DateTime(date.getTime(),
DateTimeZone.forID("Asia/Calcutta"));
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
String datetimeString = dateTimeNew.toString("yyyy-MM-dd HH:mm:ss");
long milis = 0;
try {
     milis = simpleDateFormat.parse(datetimeString).getTime();
} catch (ParseException e) {
   e.printStackTrace();
}

1

@prgDevelop的答案在我的Android Marshmallow上返回0。必须返回7200000。这些更改使其正常工作:

int offset = TimeZone.getTimeZone(Time.getCurrentTimezone()).getRawOffset() + TimeZone.getTimeZone(Time.getCurrentTimezone()).getDSTSavings();

0

这可能会帮助有相同需求的人

private String getDate(long time){
        SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy hh:mm a");
        String dateString = formatter.format(new Date(time));
        String date = ""+dateString;
        return date;
    }

0
我遇到了类似的问题。只需将UTC时间戳设置为您的时区日历并格式化日期即可。无论时区如何,时间戳仍然保持不变。
val local = Calendar.getInstance()   // get your device timezone calendar
local.timeInMillis = <timestamp>

val sdf = SimpleDateFormat("dd/MM/yyyy HH:mm:ss")
val formatted = sdf.format(Date(local.timeInMillis))

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