2015-03-30 128 views
4

如何在Linux中使用Java或C++獲取當前TAI時間(以毫秒爲單位)?如何獲取當前的TAI時間?

我需要這個的原因是能夠準確地在很長一段時間內(大約幾年)獲取時間戳,並且仍然能夠比較它們,而不用擔心閏秒。在閏秒期間進行多次測量是可能的,並且所有測量都需要明確,單調增加併線性增加。這將是一個專用的Linux服務器。這是一個需要大約0.5秒精度的科學項目。

我目前不想投資GPS計時器,並希望將NTP用於pool.ntp.org以保持系統時鐘正常運行。

我已經調查了以下解決方案:

的Java 8或ThreeTen項目 獲得TAIInstant是使用即時然後將其轉換其中,根據規範,「轉換的唯一途徑根據UTC-SLS,即時通訊在閏秒附近不會完全準確。「這本身並不是什麼大問題(事實上,使用UTC-SLS也是可以接受的)。然而,在Instant類中使用now()也似乎只是System.currentTimeMillis()的一個包裝,這讓我認爲在閏秒期間,時間仍然不明確,項目實際上不會給我TAI時間。在Java 8個規範也狀態:

使用JSR-310 API的Java時間尺度的實現都不 需要提供任何時鐘,亞秒準確,或者說 進展單調地或平滑地 。因此實現方式不需要實際執行UTC-SLS擺動或者以其他方式知道閏秒,即 。

使用權利/?時區 這似乎是可行的,但我不知道如果實施足夠聰明,可以在閏秒內繼續工作,或者System.currentTimeMillis()甚至可以給TAI時間。換句話說,底層實現是否仍然使用UTC,從而在閏秒期間給出了模糊的時間,然後將其轉換爲TAI,或者使用System.currentTimeMillis()始終使用正確/時區實際上與TAI一起工作(即,即使在閏秒)?

使用CLOCK_TAI 我試圖在Linux內核中使用CLOCK_TAI卻發現它是完全一致CLOCK_REALTIME在我的測試: 代碼:

#include <iostream> 
#include <time.h> 

long sec(int clock) 
{ 
    struct timespec gettime_now; 
    clock_gettime(clock, &gettime_now); 
    return gettime_now.tv_sec; 
} 

int main() 
{ 
    std::cout << sec(0) << std::endl;  // CLOCK_REALTIME 
    std::cout << sec(1) << std::endl;  // CLOCK_MONOTONIC 
    std::cout << sec(11) << std::endl;  // CLOCK_TAI 

    return 0; 
} 

輸出很乾脆:

1427744797 
6896 
1427744797 

使用CLOCK_MONOTONIC 問題在於時間戳需要即使計算機重新啓動,仍保持有效和可比較。

回答

2

除了正確接受的答案,我還要提到的免費Java庫Time4Jmin version v4.1)作爲可能的解決方案,因爲

  • 我寫它填補了Java世界(間隙java.time不能做所有),
  • 迄今爲止給出的其他答案只談論C++(但你也要求Java),
  • 它的工作原理與@ user3427419所描述的相同。

它使用基於System.nanoTime()的單調時鐘,但甚至允許通過接口TickProvider自定義實現。爲了進行校準,您可以使用net.time4j.SystemClock.MONOTONIC,也可以使用名爲SntpConnector的SNTP時鐘,它只需要一些簡單的配置即可連接到所需的任何NTP時間服務器。由於內置的​​閏秒錶,Time4J甚至可以在本月底向您顯示已公佈的閏秒 - 採用ISO-8601標記,甚至可以在任何時區使用格式化的本地時間戳字符串(使用i18n模塊) 。

對時鐘進行重新校準(如果是NTP - 重新連接)是可能的,這意味着時鐘可以適應中等時間調整(儘管我強烈建議您在測量期間或閏秒時不要這樣做)。雖然這種SNTP時鐘的重新連接通常會導致時間倒退,但在某些情況下,Time4J會嘗試應用平滑算法(如果在時鐘配置中激活)以確保單調行爲。詳細的文件可用online

實施例: - 基於的解決方案是更爲簡單

// Step 0: configure your clock 
String ntpServer = "ptbtime1.ptb.de"; 
SntpConnector clock = new SntpConnector(ntpServer); 

// Step 1: Timestamp start of the program and associate it with a counter 
clock.connect(); 

// Step 2: Use the counter for sequential measurements at fixed intervals 
Moment m = clock.currentTime(); 
System.out.println(m); // possible output = 2015-06-30T23:59:60,123456789Z 

// Step 3: Timestamp new counter value(s) as necessary to keep your data adequately synced 
clock.connect(); 

如果任何C++我懷疑。更多的代碼演示也可以在DZone上進行研究。


更新(答案在評論質疑):

稍微簡化的解決方案如何自動下載指定的IETF資源的新閏秒,並把它翻譯成特定Time4J格式可能是這樣的:

URL url = new URL("https://www.ietf.org/timezones/data/leap-seconds.list"); 
BufferedReader br = 
    new BufferedReader(
     new InputStreamReader(url.openStream(), "US-ASCII")); 
String line; 
PlainDate expires = null; 
Moment ntpEpoch = PlainTimestamp.of(1900, 1, 1, 0, 0).atUTC(); 
List<PlainDate> events = new ArrayList<PlainDate>(); 

try { 
    while ((line = br.readLine()) != null) { 
     if (line.startsWith("#@")) { 
      long expraw = Long.parseLong(line.substring(2).trim()); 
      expires = ntpEpoch.plus(
       expraw, TimeUnit.SECONDS) 
      .toZonalTimestamp(ZonalOffset.UTC).toDate(); 
      continue; 
     } else if (line.startsWith("#")) { 
      continue; // comment line 
     } 

     // this works for some foreseeable future 
     long epoch = Long.parseLong(line.substring(0, 10)); 

     // this is no leap second 
     // but just the official introduction of modern UTC scale 
     if (epoch == 2272060800L) { 
      continue; 
     } 

     // -1 because we don't want to associate 
     // the leap second with the following day 
     PlainDate event = 
      ntpEpoch.plus(epoch - 1, TimeUnit.SECONDS) 
        .toZonalTimestamp(ZonalOffset.UTC).toDate(); 
     events.add(event); // we don't assume any negative leap seconds here for simplicity 
    } 
} finally { 
    br.close(); 
} 

// now let's write the result into time4j-format 
// use a location relative to class path of main program (see below) 
String path = "C:/work/leapseconds.txt"; 
Writer writer = new FileWriter(new File(path)); 
String sep = System.getProperty("line.separator"); 

try { 
    for (PlainDate event : events) { 
     writer.write(event + ", +" + sep); 
    } 
    writer.write("@expires=" + expires + sep); 
} finally { 
    writer.close(); 
} 

System.out.println(
    "Leap second file was successfully written from IETF-resource."); 

// And finally, we can start the main program in a separate process 
// with the system property "net.time4j.scale.leapseconds.path" 
// set to our leapsecond file path (must be relative to class path) 

一些注意事項:

爲了避免主程序依賴互聯網連接,我建議將此代碼編寫爲由簡單批處理程序調用的子程序。這個批處理文件最終會使用所提到的系統屬性調用主程序。如果你設置了這個property,那麼將從那裏指定的文件中讀取閏秒,然後任何最終可用的tzdata模塊將停止產生任何併發的閏秒信息。

+0

你的圖書館如何處理長期的閏秒?換句話說,如果一個應用程序在未來幾年內重新啓動,我如何確保它具有過去幾年發生的閏秒知識? – 2015-10-13 01:46:49

+0

@DanielCentore可以通過自己更新您的閏秒數據導入新的閏秒 - 無論是通過新版本的庫還是通過更新tzdata模塊(事實上從iana.org/tz導入新的tzdb版本)。 Time4J還管理[過期標誌](http://time4j.net/javadoc-en/net/time4j/scale/LeapSeconds.html#getDateOfExpiration--),如果閏秒數據可用於將警報編程給系統管理員已經變老了。請記住,無法將數據提前6個月保持有效狀態。 – 2015-10-13 11:54:14

+0

是否有推薦的方法來自動從互聯網上下載資源來自動處理這個問題? – 2015-10-13 17:34:48

3

我之所以需要這個是能夠準確地採取時間戳 過的很長一段時間(兩年左右),仍然能夠 對它們進行比較,而不必擔心閏秒。可能有 用於在閏秒期間進行多次測量,並且所有測量結果都必須是明確的,單調遞增的,並且線性增加。

那麼你的設計是不理想的。你不能使用時間,然後以某種方式干涉閏秒。這實際上經常出現,人們陷入使用掛鐘進行時間戳測量的陷阱。程序和其關聯爲必要時帶計數器

  • 使用以固定的間隔計數器連續測量
  • 時間戳新的計數器值(一個或多個),以確保您的數據能夠充分同步的

    1. 時間戳的開始

    如果您避免在1秒內發生超時(午夜!)的時間戳,您可以免費入住,因爲可以稍後再調整。

    現在,如果您堅持使用TAI而不使用計數器,您所需要的只是一張需要考慮閏秒的表格。然後使用單調時間。還有,可以爲你做這個庫,但他們可能會過時的,所以你必須自己維護它們,

    http://skarnet.org/software/skalibs/libstddjb/tai.html

  • 0

    您有基於C++的std ::實現TAI時鐘steady_clock或類似的。要同步您的TAI時鐘,您可以依靠GPS或NTP。

    來自NTP的選項TAI:您的TAI實施需要關於閏秒的知識。可能NTP協議或referenced resources是當前和未來閏秒的最可靠來源。從GPS

    選項TAI:GPS時鐘的固定偏移至大,你不必惹閏秒

    3

    CLOCK_REALTIMECLOCK_TAI返回相同的,因爲內核參數tai_offset爲零。

    使用adjtimex(timex tmx)進行檢查並讀取值。我認爲ntpd會設置它,如果它足夠新(>4.2.6)並有閏秒文件。它也可以從上游服務器獲取它,但我無法驗證。以root身份運行時,調用adjtimex()可以手動設置tai_offset。您需要一個新的ish man頁面adjtimex才能查看要設置的參數。我的debian man頁面太老,但命令奏效。