【问题标题】:How to convert UTC timestamp to device local time in android如何在android中将UTC时间戳转换为设备本地时间
【发布时间】:2013-01-28 23:44:52
【问题描述】:

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

下面是我做的代码

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

【问题讨论】:

    标签: android timestamp utc


    【解决方案1】:

    java.time

    java.util 日期时间 API 及其格式化 API SimpleDateFormat 已过时且容易出错。建议完全停止使用,改用modern Date-Time API*

    使用现代日期时间 API java.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);
        }
    }
    

    在我的时区输出,欧洲/伦敦:

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

    ONLINE DEMO

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

    通过 Trail: Date Time 了解有关现代日期时间 API 的更多信息。


    * 出于任何原因,如果您必须坚持使用 Java 6 或 Java 7,您可以使用 ThreeTen-Backport,它将大部分 java.time 功能向后移植到 Java 6 和 7 . 如果您正在为一个 Android 项目工作并且您的 Android API 级别仍然不符合 Java-8,请检查 Java 8+ APIs available through desugaringHow to use ThreeTenABP in Android Project

    【讨论】:

      【解决方案2】:

      Java:

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

      科特林:

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

      【讨论】:

      • 正是我想要的。
      • 还要考虑夏令时:int gmtOffset = TimeZone.getDefault().getRawOffset() + TimeZone.getDefault().getDSTSavings();
      • 这应该是答案。
      • 这里应该是负号吧? long now = System.currentTimeMillis() - gmtOffset
      • 对不起,这个答案不正确。自纪元以来的毫秒数始终是自纪元以来的毫秒数。为时区和/或夏令时 (DST) 调整它们没有意义。如果你的代码最终给出了正确的结果,那么它仍然走错了路,肯定会让未来的读者感到困惑。
      【解决方案3】:

      我遇到了类似的问题。只需将您的 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))
      

      【讨论】:

        【解决方案4】:

        工作正常

        在你使用的地方调用这个方法

        SntpClient client = new SntpClient();
        if (client.requestTime("ntp.ubuntu.com", 30000)) {
          long now = client.getNtpTime();
          Date current = new Date(now);
        
          date2 = sdf.parse(new Date(current.getTime()).toString());
         // System.out.println(current.toString());
          Log.e(TAG, "testing SntpClient time current.toString() "+current.toString()+" , date2 = "+date2);
        }
        
        =====================================================   
        
        import android.os.SystemClock;
        import android.util.Log;
        
        import java.net.DatagramPacket;
        import java.net.DatagramSocket;
        import java.net.InetAddress;
        
        /**
         * {@hide}
         *
         * Simple SNTP client class for retrieving network time.
         *
         * Sample usage:
         * <pre>SntpClient client = new SntpClient();
         * if (client.requestTime("time.foo.com")) {
         *     long now = client.getNtpTime() + SystemClock.elapsedRealtime() - client.getNtpTimeReference();
         * }
         * </pre>
         */
        public class SntpClient
        {
          private static final String TAG = "SntpClient";
        
          private static final int REFERENCE_TIME_OFFSET = 16;
          private static final int ORIGINATE_TIME_OFFSET = 24;
          private static final int RECEIVE_TIME_OFFSET = 32;
          private static final int TRANSMIT_TIME_OFFSET = 40;
          private static final int NTP_PACKET_SIZE = 48;
        
          private static final int NTP_PORT = 123;
          private static final int NTP_MODE_CLIENT = 3;
          private static final int NTP_VERSION = 3;
        
          // Number of seconds between Jan 1, 1900 and Jan 1, 1970
          // 70 years plus 17 leap days
          private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;
        
          // system time computed from NTP server response
          private long mNtpTime;
        
          // value of SystemClock.elapsedRealtime() corresponding to mNtpTime
          private long mNtpTimeReference;
        
          // round trip time in milliseconds
          private long mRoundTripTime;
        
          /**
           * Sends an SNTP request to the given host and processes the response.
           *
           * @param host host name of the server.
           * @param timeout network timeout in milliseconds.
           * @return true if the transaction was successful.
           */
          public boolean requestTime(String host, int timeout) {
            DatagramSocket socket = null;
            try {
              socket = new DatagramSocket();
              socket.setSoTimeout(timeout);
              InetAddress address = InetAddress.getByName(host);
              byte[] buffer = new byte[NTP_PACKET_SIZE];
              DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);
        
              // set mode = 3 (client) and version = 3
              // mode is in low 3 bits of first byte
              // version is in bits 3-5 of first byte
              buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);
        
              // get current time and write it to the request packet
              long requestTime = System.currentTimeMillis();
              long requestTicks = SystemClock.elapsedRealtime();
              writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);
        
              socket.send(request);
        
              // read the response
              DatagramPacket response = new DatagramPacket(buffer, buffer.length);
              socket.receive(response);
              long responseTicks = SystemClock.elapsedRealtime();
              long responseTime = requestTime + (responseTicks - requestTicks);
        
              // extract the results
              long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
              long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
              long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
              long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
              // receiveTime = originateTime + transit + skew
              // responseTime = transmitTime + transit - skew
              // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
              //             = ((originateTime + transit + skew - originateTime) +
              //                (transmitTime - (transmitTime + transit - skew)))/2
              //             = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
              //             = (transit + skew - transit + skew)/2
              //             = (2 * skew)/2 = skew
              long clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2;
              // if (false) Log.d(TAG, "round trip: " + roundTripTime + " ms");
              // if (false) Log.d(TAG, "clock offset: " + clockOffset + " ms");
        
              // save our results - use the times on this side of the network latency
              // (response rather than request time)
              mNtpTime = responseTime + clockOffset;
              mNtpTimeReference = responseTicks;
              mRoundTripTime = roundTripTime;
            } catch (Exception e) {
              if (false) Log.d(TAG, "request time failed: " + e);
              return false;
            } finally {
              if (socket != null) {
                socket.close();
              }
            }
        
            return true;
          }
        
          /**
           * Returns the time computed from the NTP transaction.
           *
           * @return time value computed from NTP server response.
           */
          public long getNtpTime() {
            return mNtpTime;
          }
        
          /**
           * Returns the reference clock value (value of SystemClock.elapsedRealtime())
           * corresponding to the NTP time.
           *
           * @return reference clock corresponding to the NTP time.
           */
          public long getNtpTimeReference() {
            return mNtpTimeReference;
          }
        
          /**
           * Returns the round trip time of the NTP transaction
           *
           * @return round trip time in milliseconds.
           */
          public long getRoundTripTime() {
            return mRoundTripTime;
          }
        
          /**
           * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
           */
          private long read32(byte[] buffer, int offset) {
            byte b0 = buffer[offset];
            byte b1 = buffer[offset+1];
            byte b2 = buffer[offset+2];
            byte b3 = buffer[offset+3];
        
            // convert signed bytes to unsigned values
            int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
            int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
            int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
            int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);
        
            return ((long)i0 << 24) + ((long)i1 << 16) + ((long)i2 << 8) + (long)i3;
          }
        
          /**
           * Reads the NTP time stamp at the given offset in the buffer and returns
           * it as a system time (milliseconds since January 1, 1970).
           */
          private long readTimeStamp(byte[] buffer, int offset) {
            long seconds = read32(buffer, offset);
            long fraction = read32(buffer, offset + 4);
            return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);
          }
        
          /**
           * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp
           * at the given offset in the buffer.
           */
          private void writeTimeStamp(byte[] buffer, int offset, long time) {
            long seconds = time / 1000L;
            long milliseconds = time - seconds * 1000L;
            seconds += OFFSET_1900_TO_1970;
        
            // write seconds in big endian format
            buffer[offset++] = (byte)(seconds >> 24);
            buffer[offset++] = (byte)(seconds >> 16);
            buffer[offset++] = (byte)(seconds >> 8);
            buffer[offset++] = (byte)(seconds >> 0);
        
            long fraction = milliseconds * 0x100000000L / 1000L;
            // write fraction in big endian format
            buffer[offset++] = (byte)(fraction >> 24);
            buffer[offset++] = (byte)(fraction >> 16);
            buffer[offset++] = (byte)(fraction >> 8);
            // low order bits should be random data
            buffer[offset++] = (byte)(Math.random() * 255.0);
          }
        }
        

        【讨论】:

          【解决方案5】:

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

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

          【讨论】:

            【解决方案6】:

            我在kotlin 中使用Extension Functions 完成此操作

            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"
            

            注意:

            确保正确验证。

            【讨论】:

              【解决方案7】:

              将格式为“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;
              }
              

              【讨论】:

              • 这是整个 SO 的最佳答案。谢谢!
              • 这是否也考虑夏令时?
              • 这很完美。但是假设我正在尝试将 2018-04-27 12:48:00 UTC 时区转换为 IST 时区。现在在这里它在转换后返回 2018-04-27 06:18:00 am 而不是 2018-04-27 06:18:00 pm 在这种情况下该怎么办?如果任何 UTC 时间介于 12:00:00 到 12:59:59 之间。这返回错误的结果。在其他工作中完美。
              • @madhusudhan 如何转换这个 2018-12-01T20:21:36.793194+05:30
              【解决方案8】:

              这可能对有相同要求的人有所帮助

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

              【讨论】:

                【解决方案9】:

                我已经做了这样的事情来从 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 吗?
                【解决方案10】:

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

                【讨论】:

                  【解决方案11】:

                  您示例中的代码乍一看还不错。顺便说一句,如果服务器时间戳采用 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。

                  如果打印的服务器时间是您期望的日期,那么您不必对其应用 rawoffset。所以你的代码会更简单(减去所有的调试日志):

                  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。任何解决方法......谢谢
                  • 您的代码示例对我来说看起来不错,这应该会给您时间差(例如 2 分钟前)。但是,我认为您不必将 rawoffset 应用于服务器时间(假设服务器时间为 UTC)。我需要更多信息来提供帮助。根据我的代码示例,您是否打印了服务器时间,是您期望的时间吗?
                  • 是的,你的代码给了我标准格式的时间,但我想显示 17 分钟前的时间......
                  • pestrella 我的代码给了我我所期望的,但时差是 5 小时。任何想法
                  • 出于好奇,您正在使用的服务器时间戳是什么?此外,如果您将 DateUtils.MINUTE_IN_MILLIS 更改为 0,那么您将获得以秒为单位的经过时间(例如 2 秒前)。你能用 0 代替 MINUTE_IN_MILLIS 做另一个测试吗?记录服务器时间戳。你得到了什么时间戳?你的 TextView 显示了什么?
                  猜你喜欢
                  • 2013-03-23
                  • 2019-08-15
                  • 2011-02-06
                  • 2019-03-09
                  • 2012-09-11
                  • 1970-01-01
                  • 2019-01-05
                  • 2018-09-17
                  • 1970-01-01
                  相关资源
                  最近更新 更多