Pytanie i oczekiwania
Chociaż dosłowna forma tego pytania jest praktyczna w kontekście (rok 1899), jest nieco niejasna w sensie teoretycznym. Ile lat ma lat? Jak daleko w przeszłość chcielibyśmy się posunąć? Co z przyszłością?
Ponieważ WordPress zaczął jako silnik do blogowania, w tym kontekście kontekstowym ewoluował, aby obsługiwać następujący przedział czasu:
- daty WP istniało (oczywiście, aby móc z niego korzystać)
- zakres możliwych historycznych postów (domyślnie już w czasach istnienia Internetu)
- jak najdalej w przyszłość, bez specjalnego wysiłku (pracuj, aż się zepsuje)
Gdy korzystanie z WordPress przekształciło się w aplikacje nie blogujące, takie projekty (zwykle historia i sztuka, jak widziałem w raportach) zaczęły napotykać różne problemy z datami spoza tego zakresu.
Na potrzeby moich badań sformułowałem następujące pytania:
- Jakie są dwa najwcześniejsze i ostatnie pełne lata kalendarzowe, których można używać z datami postów WordPress w sposób natywny i niezawodny?
- Jakie są nisko wiszące owoce (jeśli występują), aby przedłużyć dostępny zakres poza natywny zasięg?
Ograniczenia platformy
Ponieważ WordPress jest aplikacją PHP i korzysta z MySQL do przechowywania danych, podlega ich ograniczeniom.
MySQL
WordPress przechowuje daty postów w post_date
kolumnie DATETIME
typu w MySQL.
Zgodnie z dokumentacją ten typ obsługuje lata od 1000 do 9999 :
Ten DATETIME
typ jest używany dla wartości, które zawierają zarówno części daty, jak i godziny. MySQL pobiera i wyświetla DATETIME
wartości w 'YYYY-MM-DD HH:MM:SS'
formacie. Obsługiwany zakres jest '1000-01-01 00:00:00'
do '9999-12-31 23:59:59'
.
Jednak mówi również, że wcześniejsze wartości mogą działać, nie wspominając o późniejszych wartościach:
W przypadku DATE and DATETIME
opisów zakresów „obsługiwane” oznacza, że chociaż wcześniejsze wartości mogą działać, nie ma gwarancji.
Chociaż empirycznie zaobserwowałem wartości poza zakresem działania, jest to anegdota i wypada z naszego stanu niezawodności.
PHP
W programowaniu PHP powszechnie stosowana jest uniksowa reprezentacja datownika. Zgodnie z dokumentacją do naszych celów (PHP 5.2+ i ogólne 32-bitowe środowisko) obsługuje lata (w całości) 1902 do 2037 :
Prawidłowy zakres znacznika czasu to zazwyczaj od Fri, 13 Dec 1901 20:45:54 UTC
do Tue, 19 Jan 2038 03:14:07 UTC
. (Są to daty, które odpowiadają minimalnym i maksymalnym wartościom dla 32-bitowej liczby całkowitej ze znakiem). Ponadto nie wszystkie platformy obsługują ujemne znaczniki czasu, dlatego zakres dat może być ograniczony nie wcześniej niż w epoce Uniksa. Oznacza to, że np. Wcześniejsze daty Jan 1, 1970
nie będą działać w systemie Windows, niektórych dystrybucjach Linuksa i kilku innych systemach operacyjnych. PHP 5.1.0 i nowsze wersje pokonują jednak to ograniczenie.
Poza tym nowsza Date/Time
obsługa oparta jest na wersji 64-bitowej i ma zakres od około 292 miliardów do 292 miliardów lat , co prawdopodobnie przekracza potrzeby ludzkości w tym czasie.
Ograniczenia WordPress
WordPress wprowadza i dziedziczy pewne dodatkowe ograniczenia w swojej podstawie kodu.
Przepływ danych
Z punktu widzenia podstawowego przepływu pracy użytkownika są przetwarzane dwa powiązane z datą:
- Wprowadzanie daty w formularzu edycji postu musi być poprawnie przetworzone i zapisane w bazie danych
- data zapisana w bazie danych musi być poprawnie odczytana i pokazana w interfejsie
Należy pamiętać, że są to technicznie całkowicie różne i niezależne procesy. Jak wyjaśniono dalej, ich zakresy nie nakładają się, a zapisanie prawidłowej daty nie oznacza zdolności do prawidłowego odczytu jej w środowisku WordPress.
Jawne ograniczenia
- Edytor postów WordPress w admin pozwala na zakres lat, które można przesłać jako datę ogłoszenia, od 100 do 9999
_wp_translate_postdata()
przetwarza rok (przesłany jako numer odrębny od formularza) i:
- dezynfekuje go do wartości nieujemnych > 0
- sprawdza poprawność za pomocą
wp_checkdate()
, który wywołuje PHP natywny checkdate()
, co nakłada limit od 1 do 32767
Domniemane limity
strtotime()
Funkcja PHP jest używana wiele razy i podlega wyżej wymienionemu znacznikowi czasu Unix, na najniższym poziomie, mysql2date()
który wpływa na wszystkie odczyty dat z bazy danych, dziedziczony zakres od 1902 do 2037
- WordPress wraca do wyrażenia regularnego do analizowania daty
get_gmt_from_date()
, który oczekuje roku ([0-9]{1,4})
, ograniczając go do 1 9999 , silna możliwość podobnego przetwarzania w innych funkcjach, które będą wymagały dokładniejszego audytu kodu
Możliwość obejścia
wp_checkdate()
ma wp_checkdate
filtr, który pozwala zastąpić tę kontrolę sprawdzania poprawności
- wyjście skierowane do użytkownika końcowego przechodzi przez
date_i18n()
który ma date_i18n
filtr, teoretycznie pozwala całkowicie przechwycić i ponownie przetworzyć dane wyjściowe do interfejsu, jednak trudne, jeśli funkcja jest już przekazana poza false
wejście znacznika czasu poza zakresem ( )
Wnioski
Ze względów praktycznych i możliwości przenoszenia danych zakres dat publikacji WordPress wydaje się być równy 32-bitowemu znacznikowi czasu Unix i obejmuje lata 1902–2037 włącznie .
W przypadku każdej operacji po dacie poza środowiskiem należy skontrolować środowisko (64-bitowy zakres uniksowych znaczników czasu, de facto działający MySQL lub alternatywna pamięć bazy danych dla wartości). W przypadku dalszych zakresów ( poniżej 1000, powyżej 9999 ) prawdopodobnie wymagane będą znaczne ilości niestandardowego kodu.
W przypadku dowolnej realizacji dowolnych dat sensowne jest:
- przechowuj je w MySQL w formacie niepodlegającym ograniczeniom bazy danych
- proces w PHP przy użyciu całkowicie niestandardowego
Date/Time
kodu i / lub kontrolowanych funkcji WordPress, na które nie mają wpływu ograniczenia uniksowego znacznika czasu
Łóżko testowe do kodu
Poniższy kod i ręcznie wybrany zestaw lat zostały wykorzystane do powyższych badań i testowania wniosków:
require ABSPATH . '/wp-admin/includes/post.php';
$timestamp_size_info = array(
'PHP_INT_SIZE' => PHP_INT_SIZE,
'PHP_INT_MAX' => number_format( PHP_INT_MAX ),
'min timestamp' => date( DATE_ISO8601, - PHP_INT_MAX ),
'zero timestamp' => date( DATE_ISO8601, 0 ),
'max timestamp' => date( DATE_ISO8601, PHP_INT_MAX ),
);
r( $timestamp_size_info );
// hand picked set of years to test for assorted limits
$years = array(
'negative' => - 1,
'zero' => 0,
'one' => 1,
'wp min' => 100,
'mysql first' => 1000,
'before unix' => 1899,
'unix first' => 1902,
'current' => 2013,
'unix last' => 2037,
'after unix' => 2039,
'mysql last, wp max' => 9999,
'after checkdate' => 33000,
);
// simulates form submission data
$post = array(
'post_type' => 'post', // shut notice
'edit_date' => 1,
'aa' => 1,
'mm' => '01',
'jj' => '01',
'hh' => '00',
'mn' => '00',
'ss' => '00',
);
// add_filter( 'wp_checkdate', '__return_true' );
foreach ( $years as $name => $year ) {
$post['aa'] = $year;
$translated = _wp_translate_postdata( false, $post );
if ( is_wp_error( $translated ) ) { // wp_checkdate() failed
r( array( 'year' => $year . " ({$name})", 'translated valid' => false ) );
}
else {
$post_date = $translated['post_date'];
$post_date_gmt = $translated['post_date_gmt'];
$translated_valid = (string) $year == substr( $post_date, 0, strpos( $post_date, '-' ) );
$mysql2date = mysql2date( DATE_ISO8601, $post_date );
$mysql2date_valid = (string) $year == substr( $mysql2date, 0, strpos( $mysql2date, '-' ) );
r( array(
'year' => $year . " ({$name})",
'post_date' => $post_date,
'translated valid' => $translated_valid,
'post_date_gmt' => $post_date_gmt,
'mysql2date' => $mysql2date,
'from sql valid' => $mysql2date_valid,
) );
}
}