2010-09-15 30 views
22

Thư viện và dịch vụ web tôi đang sử dụng, giao tiếp khoảng thời gian trong ISO 8601: PnYnMnDTnHnMnS. Tôi muốn chuyển đổi các định dạng này thành giây. Và ngược lại. Giây dễ dàng hơn nhiều để tính toán.Phân tích cú pháp và tạo ISO 8601 Ngày và khoảng thời gian, như PT15M trong PHP

Ví dụ giá trị khoảng là:

  • PT1M hoặc PT60S (1 phút)
  • PT1H, PT60M hoặc PT3600S (1 giờ)

tôi cần hai chức năng: phân tích từ các giá trị đó cho giây: iso8601_interval_to_seconds() và từ giây thành các khoảng thời gian như vậy: iso8601_interval_from_seconds().

Sau này là khá đơn giản, bởi vì nó có thể được thực hiện như là "PT {$ giây} S", chỉ cần vượt qua giây dọc theo, mọi lúc. Có lẽ điều này có thể được thực hiện đẹp hơn với một trình phân tích cú pháp chuyển sang H (giờ) hoặc M (phút)?

Đầu tiên khó hơn, nhưng có thể có một thủ thuật với một trong nhiều trình chuyển đổi chuỗi theo ngày trong PHP? Tôi rất thích tìm hiểu cách sử dụng một hàm như vậy để phân tích các khoảng thời gian. Hoặc học cách thay thế.

+0

Phạt cảnh cáo rất muộn ... chuyển đổi P1MT đến giây sẽ không thể trừ khi bạn biết liệu bạn đang chuyển đổi một tháng với 28, 29, 30, hoặc 31 ngày. Đối với vấn đề đó, bạn thậm chí không thể chuyển đổi P1YT thành 365 ngày mà không bị sai một năm trong bốn. (Có một lý do tại sao khoảng thời gian không được chỉ định trong ngày hoặc giây trong tiêu chuẩn.) – Bevan

+0

@Bevan: Câu trả lời của Lee đi vào chi tiết với điều này. TL; DR: bạn cần một ngày bắt đầu. – berkes

+0

Ahh - cảm ơn @berkes, tôi đã bỏ lỡ khi lướt qua trang (tôi đã tìm thấy nó khi đang tìm kiếm thứ gì đó không liên quan). – Bevan

Trả lời

17

Dường như bản PHP DateInterval của PHP 5.3 hỗ trợ điều này.

Nếu bạn không thể sử dụng 5.3, tôi cho rằng bất kỳ chức năng chuyển đổi nào như vậy sẽ biết số giây trong một năm, một tháng, một ngày, một giờ và một phút. Sau đó, khi chuyển đổi từ giây, nó sẽ chia theo thứ tự đó, mỗi lần lấy modulo của thao tác trước đó, cho đến khi chỉ còn < 60 giây còn lại. Khi chuyển đổi từ một biểu diễn khoảng thời gian ISO 8601, nó sẽ không quan trọng để phân tích cú pháp chuỗi và nhân mỗi phần tử tìm thấy tương ứng.

+1

DateInterval ('PT1H') hoạt động giống như quyến rũ!Bây giờ, tôi có thể muốn xem xét một cách tốt hơn để xây dựng lại các chuỗi này, sau đó PTXXXS, everyting trong giây :) – berkes

+0

⚠️ 'DateInterval()' của PHP có lỗi không cho phép phân số trong các biểu diễn ISO 8601 mà nó phân tích cú pháp : https://bugs.php.net/bug.php?id=53831 –

0

gì bạn đang tìm kiếm là DateTime :: diff

Đối tượng DateInterval đại diện cho sự khác biệt giữa hai ngày hoặc FALSE về thất bại như minh họa dưới đây:

$datetime1 = new DateTime('2009-10-11'); 
$datetime2 = new DateTime('2009-10-13'); 
$interval = $datetime1->diff($datetime2); 
echo $interval->format('%R%d days'); 

Chỉ cần sử dụng giây thay vì ngày.

Đây là từ http://www.php.net/manual/en/datetime.diff.php

Đối với một tham chiếu đến DateInterval thấy http://www.php.net/manual/en/class.dateinterval.php

+0

Tôi không thấy làm thế nào điều này giúp để chuyển đổi và fro các định dạng PT10M -alike ISO 8601. – berkes

7

Hãy nhận biết rằng chuyển đổi khoảng thời gian có chứa Ngày, Tháng, và/hoặc năm vào một thời gian như giây không thể được thực hiện một cách chính xác mà không biết ngày/giờ bắt đầu thực tế.

Ví dụ

1 SEP 2010: +P1M2D means +2764800 seconds 
1 OCT 2010: +P1M2D means +2851200 seconds 

Đó là bởi vì tháng chín có 30 ngày và tháng Mười có 31 ngày. Cùng một vấn đề xảy ra với việc chuyển đổi khoảng thời gian năm, bởi vì năm nhuận và bước nhảy vọt. Leap-năm giới thiệu phức tạp hơn nữa để chuyển đổi tháng là tốt - kể từ tháng Hai là một ngày dài hơn trong năm nhuận hơn là nếu không. Ngày có vấn đề ở những khu vực mà thời gian tiết kiệm ánh sáng ban ngày được thực hiện - một khoảng thời gian một ngày xảy ra trong quá trình chuyển đổi DST, thực sự dài hơn 1 giờ so với thời gian khác.

Tất cả những gì được nói - tất nhiên, bạn có thể nén các giá trị chứa chỉ Giờ, Phút và Giây vào giá trị chỉ chứa Giây. Tôi khuyên bạn nên xây dựng một trình phân tích cú pháp đơn giản để thực hiện công việc (hoặc có thể xem xét một biểu thức chính quy).

Chỉ cần lưu ý những cạm bẫy được nêu ở trên - có con rồng. Nếu bạn có ý định đối phó với Ngày, Tháng và/hoặc Năm, bạn cần phải sử dụng một trong các cơ chế tích hợp để thực hiện phép tính cho bạn trong ngữ cảnh của một ngày/giờ đã biết. Như những người khác đã đề cập: lớp DateInterval, kết hợp với các hàm được cung cấp trong lớp DateTime có lẽ là cách trực quan nhất để giải quyết vấn đề này. Nhưng điều đó chỉ có sẵn trong phiên bản PHP 5.3.0 trở lên.

Nếu bạn phải làm việc với ít hơn v5.3.0, bạn có thể cố gắng xây dựng một cái gì đó xung quanh đá quý này chút:

$startDateTime = '19700101UTC'; 
$duration = strtotime($startDateTime.'+1Year1Day1Second') - strtotime($startDateTime); 
print("Duration in Seconds: $duration"); 

strtotime sẽ không làm việc với các định dạng ISO 8601 trực tiếp (ví dụ P1Y1DT1S.) , nhưng định dạng mà nó không hiểu (1Year1Day1Second) không phải là quá xa - nó sẽ là một chuyển đổi khá thẳng về phía trước. (một chút "hacky" ... nhưng đó là PHP cho bạn).

chúc may mắn!

+0

Suy nghĩ về điều này một cách trực quan tôi muốn viết lại số giây tuyệt đối là năm + tháng + ngày + giờ + phút + giây để làm cho nó dễ đọc hơn sẽ không thay đổi giá trị thực của nó, nghĩa là bất cứ nơi nào có khoảng thời gian biến đổi giá trị sẽ được chọn. Vì vậy, trong nhiều tháng, đó sẽ là 30 ngày = 1 tháng theo lịch. Tôi đang cố gắng tìm chính xác nơi họ chỉ định điều này trong bản thân tiêu chuẩn nhưng đã đến trễ và tôi chỉ có thể xem bản nháp cuối cùng mà không phải trả tiền: http://xml.coverpages.org/ISO-FDIS-8601.pdf – Fanis

+5

Tại đầu tiên nó * * có vẻ như đó sẽ là câu trả lời trực quan, nhưng nếu bạn thực sự nghĩ về nó, nó trở nên rõ ràng rằng mọi thứ thực sự không thể làm việc như thế. Hóa ra "thời gian lịch" (ngày, tháng, năm) cũng quan trọng như "thời gian tuyệt đối" (giây, phút, giờ) - khi chúng ta sử dụng thời gian lịch, chúng ta có ý nghĩa gì đó khác với thời gian tuyệt đối. Ví dụ: Sinh nhật của bạn cách xa nhau bao xa? Luôn luôn 1 năm, không phải lúc nào cũng 8760 giờ. Nếu hóa đơn thẻ tín dụng của tháng trước là ngày 21 tháng 8, ngày nào sẽ là ngày của tháng này? 21 tháng 9. Luôn luôn 1 tháng, không phải 30 ngày. – Lee

8

strtotime sẽ không làm việc với các định dạng ISO 8601 trực tiếp (ví dụ P1Y1DT1S.), nhưng định dạng mà nó hiểu (1Year1Day1Second) không phải là quá xa - nó sẽ chuyển đổi khá thẳng về phía trước . (một chút "hacky" ... nhưng đó là PHP cho bạn).

Cảm ơn Lee, tôi không biết strtotime chấp nhận định dạng này. Đây là phần thiếu của câu đố của tôi. Có lẽ chức năng của tôi có thể hoàn thành câu trả lời của bạn.

function parse_duration($iso_duration, $allow_negative = true){ 
    // Parse duration parts 
    $matches = array(); 
    preg_match('/^(-|)?P([0-9]+Y|)?([0-9]+M|)?([0-9]+D|)?T?([0-9]+H|)?([0-9]+M|)?([0-9]+S|)?$/', $iso_duration, $matches); 
    if(!empty($matches)){  
     // Strip all but digits and - 
     foreach($matches as &$match){ 
      $match = preg_replace('/((?!([0-9]|-)).)*/', '', $match); 
     } 
     // Fetch min/plus symbol 
     $result['symbol'] = ($matches[1] == '-') ? $matches[1] : '+'; // May be needed for actions outside this function. 
     // Fetch duration parts 
     $m = ($allow_negative) ? $matches[1] : ''; 
     $result['year'] = intval($m.$matches[2]); 
     $result['month'] = intval($m.$matches[3]); 
     $result['day'] = intval($m.$matches[4]); 
     $result['hour'] = intval($m.$matches[5]); 
     $result['minute'] = intval($m.$matches[6]); 
     $result['second'] = intval($m.$matches[7]);  
     return $result; 
    } 
    else{ 
     return false; 
    } 
} 

Chức năng cũng hỗ trợ định dạng phủ định. -P10Y9MT7M5S sẽ trả về một mảng như sau: [năm] => -10 [tháng] => -9 [ngày] => 0 [giờ] => 0 [phút] => -7 [giây] = > -5 Nếu hành vi này không được mong muốn, hãy chuyển sai thành tham số thứ hai. Bằng cách này, hàm sẽ luôn trả về giá trị dương. Biểu tượng phút/dấu cộng sẽ vẫn có sẵn trong khóa kết quả ['symbol'].

Và cập nhật một chút: Chức năng này sử dụng hàm đầu tiên để nhận tổng số giây.

function get_duration_seconds($iso_duration){ 
    // Get duration parts 
    $duration = parse_duration($iso_duration, false); 
    if($duration){ 
     extract($duration); 
     $dparam = $symbol; // plus/min symbol 
     $dparam .= (!empty($year)) ? $year . 'Year' : ''; 
     $dparam .= (!empty($month)) ? $month . 'Month' : ''; 
     $dparam .= (!empty($day)) ? $day . 'Day' : ''; 
     $dparam .= (!empty($hour)) ? $hour . 'Hour' : ''; 
     $dparam .= (!empty($minute)) ? $minute . 'Minute' : ''; 
     $dparam .= (!empty($second)) ? $second . 'Second' : ''; 
     $date = '19700101UTC'; 
     return strtotime($date.$dparam) - strtotime($date); 
    } 
    else{ 
     // Not a valid iso duration 
     return false; 
    } 
} 

$foo = '-P1DT1S'; 
echo get_duration_seconds($foo); // Output -86399 
$bar = 'P1DT1S'; 
echo get_duration_seconds($bar); // Output 86401 
+0

Xin chào, bây giờ có phương pháp đơn giản hơn với PHP 5.4 không? Không phải là tôi không muốn cảm ơn bạn rất nhiều về chức năng này, nhưng có lẽ đã có một phương pháp rõ ràng hơn. – andreszs

+0

Tôi đã viết tuổi này trước đây heheh. Tôi đoán tôi có thể viết lại hàm. Ít thời gian, nhưng tôi sẽ kiểm tra sau. –

+0

Bạn có thể giải thích các ký tự '|' ống? Chúng không được theo sau bởi một nhánh thay thế, nhưng chúng không được đặt trước bởi dấu hỏi '? |' Để cùng tham chiếu với nhau. http://php.net/manual/de/regexp.reference.subpatterns.php Một thay thế rỗng là không cần thiết, bởi vì định lượng '?' được sử dụng. – CoDEmanX

1
function parsePTTime($pt_time) 
{ 
    $string = str_replace('PT', '', $pt_time); 
    $string = str_replace('H', 'Hour', $string); 
    $string = str_replace('M', 'Minute', $string); 
    $string = str_replace('S', 'Second', $string); 

    $startDateTime = '19700101UTC'; 
    $seconds = strtotime($startDateTime . '+' . $string) - strtotime($startDateTime); 

    return $seconds; 
} 

Các xét nghiệm:

PT1H   - OK 
PT23M  - OK 
PT45S  - OK 
PT1H23M  - OK 
PT1H45S  - OK 
PT23M45S  - OK 
PT1H23M45S - OK