Jaka jest różnica między DateTime
i Time
klas w Ruby i jakie czynniki spowodowałoby mi wybrać jedną lub drugą stronę?
Jaka jest różnica między DateTime
i Time
klas w Ruby i jakie czynniki spowodowałoby mi wybrać jedną lub drugą stronę?
Odpowiedzi:
Nowsze wersje Ruby (2.0+) tak naprawdę nie mają znaczących różnic między dwiema klasami. Niektóre biblioteki będą używać jednej lub drugiej ze względów historycznych, ale nowy kod niekoniecznie musi dotyczyć. Wybieranie jednego w celu uzyskania spójności jest prawdopodobnie najlepsze, więc spróbuj połączyć się z oczekiwaniami bibliotek. Na przykład ActiveRecord woli DateTime.
W wersjach wcześniejszych niż Ruby 1.9 i na wielu systemach czas jest reprezentowany jako 32-bitowa wartość ze znakiem opisująca liczbę sekund od 1 stycznia 1970 roku UTC, cienkie opakowanie wokół time_t
wartości standardu POSIX , i jest ograniczona:
Time.at(0x7FFFFFFF)
# => Mon Jan 18 22:14:07 -0500 2038
Time.at(-0x7FFFFFFF)
# => Fri Dec 13 15:45:53 -0500 1901
Nowsze wersje Ruby są w stanie obsługiwać większe wartości bez powodowania błędów.
DateTime to podejście oparte na kalendarzu, w którym rok, miesiąc, dzień, godzina, minuta i sekunda są przechowywane osobno. Jest to konstrukcja Ruby on Rails, która służy jako opakowanie wokół standardowych pól SQL DATETIME. Zawierają one dowolne daty i mogą reprezentować prawie dowolny punkt w czasie, ponieważ zakres wyrażania jest zazwyczaj bardzo duży.
DateTime.new
# => Mon, 01 Jan -4712 00:00:00 +0000
Dlatego uspokajające jest to, że DateTime może obsłużyć posty na blogu od Arystotelesa.
Wybierając jedną, różnice są teraz nieco subiektywne. Historycznie DateTime zapewnia lepsze opcje manipulowania nim w sposób kalendarzowy, ale wiele z tych metod zostało przeniesionych również do Time, przynajmniej w środowisku Rails.
[Edytuj lipiec 2018 r.]
Wszystkie poniższe elementy są nadal aktualne w Ruby 2.5.1. Z dokumentacji referencyjnej :
DateTime nie uwzględnia sekund przestępnych, nie śledzi żadnych reguł dotyczących czasu letniego.
To, czego wcześniej nie zauważono w tym wątku, jest jedną z niewielu zalet DateTime
: jest świadomy reform kalendarza, podczas gdy Time
nie jest:
[…] Klasa Ruby Time realizuje proleptyczny kalendarz gregoriański i nie ma pojęcia reformy kalendarza […].
Dokumentacja referencyjna kończy się zaleceniem, które należy stosować Time
wyłącznie w przypadku bliskich, bieżących lub przyszłych dat / godzin i stosować tylko DateTime
wtedy, gdy na przykład urodziny Szekspira wymagają dokładnej konwersji: (podkreślenie dodane)
Więc kiedy powinieneś używać DateTime w Ruby i kiedy powinieneś używać Time? Niemal na pewno będziesz chciał użyć Time, ponieważ Twoja aplikacja prawdopodobnie zajmuje się bieżącymi datami i godzinami. Jeśli jednak musisz poradzić sobie z datami i godzinami w kontekście historycznym, powinieneś użyć DateTime […]. Jeśli masz również do czynienia ze strefami czasowymi, powodzenia - pamiętaj, że prawdopodobnie będziesz mieć do czynienia z lokalnymi czasami słonecznymi, ponieważ dopiero w XIX wieku wprowadzenie kolei wymagało czasu standardowego i ostatecznie strefy czasowe.
[/ Edytuj lipiec 2018 r.]
Począwszy od Ruby 2.0, większość informacji zawartych w innych odpowiedziach jest nieaktualna.
W szczególności Time
jest teraz praktycznie niezwiązany. Może być mniej więcej niż 63 bity od Epoki:
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> Time.at(2**62-1).utc # within Integer range
=> 146138514283-06-19 07:44:38 UTC
irb(main):003:0> Time.at(2**128).utc # outside of Integer range
=> 10783118943836478994022445751222-08-06 08:03:51 UTC
irb(main):004:0> Time.at(-2**128).utc # outside of Integer range
=> -10783118943836478994022445747283-05-28 15:55:44 UTC
Jedyną konsekwencją użycia większych wartości powinna być wydajność, która jest lepsza, gdy Integer
stosuje się s (vs. Bignum
s (wartości poza Integer
zakresem) lub Rational
s (gdy śledzone są nanosekundy)):
Od wersji Ruby 1.9.2 implementacja Time używa podpisanej 63-bitowej liczby całkowitej, Bignum lub Rational. Liczba całkowita jest liczbą nanosekund od epoki, która może reprezentować 1823-11-12 do 2116-02-20. Gdy używany jest Bignum lub Rational (przed 1823 r., Po 2116 r., W nanosekundach), czas działa wolniej, jak w przypadku użycia liczby całkowitej. ( http://www.ruby-doc.org/core-2.1.0/Time.html )
Innymi słowy, o ile rozumiem, DateTime
nie obejmuje już szerszego zakresu potencjalnych wartości niżTime
.
Ponadto DateTime
prawdopodobnie należy zauważyć dwa wcześniej niewymienione ograniczenia :
DateTime nie uwzględnia żadnych przerw, nie śledzi żadnych reguł dotyczących czasu letniego. ( http://www.ruby-doc.org/stdlib-2.1.0/libdoc/date/rdoc/Date.html#class-Date-label-DateTime )
Po pierwsze, DateTime
nie ma pojęcia sekund przestępnych:
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.new(2012,6,30,23,59,60,0)
=> 2012-06-30 23:59:60 +0000
irb(main):004:0> dt = t.to_datetime; dt.to_s
=> "2012-06-30T23:59:59+00:00"
irb(main):005:0> t == dt.to_time
=> false
irb(main):006:0> t.to_i
=> 1341100824
irb(main):007:0> dt.to_time.to_i
=> 1341100823
Aby powyższy przykład działał Time
, system operacyjny musi obsługiwać sekundy przestępne, a informacje o strefie czasowej muszą być ustawione poprawnie, np. Poprzez TZ=right/UTC irb
(w wielu systemach uniksowych).
Po drugie, DateTime
ma bardzo ograniczone rozumienie stref czasowych, a w szczególności nie ma koncepcji oszczędności w świetle dziennym . Prawie obsługuje strefy czasowe jako proste przesunięcia UTC + X:
irb(main):001:0> RUBY_VERSION
=> "2.0.0"
irb(main):002:0> require "date"
=> true
irb(main):003:0> t = Time.local(2012,7,1)
=> 2012-07-01 00:00:00 +0200
irb(main):004:0> t.zone
=> "CEST"
irb(main):005:0> t.dst?
=> true
irb(main):006:0> dt = t.to_datetime; dt.to_s
=> "2012-07-01T00:00:00+02:00"
irb(main):007:0> dt.zone
=> "+02:00"
irb(main):008:0> dt.dst?
NoMethodError: undefined method `dst?' for #<DateTime:0x007f34ea6c3cb8>
Może to powodować problemy, gdy czasy są wprowadzane jako czas letni, a następnie konwertowane na strefę czasową inną niż czas letni, bez śledzenia prawidłowych przesunięć poza DateTime
sobą (wiele systemów operacyjnych może już to załatwić).
Ogólnie rzecz biorąc, powiedziałbym, że obecnie Time
jest lepszym wyborem dla większości aplikacji.
Zwróć też uwagę na istotną różnicę przy dodawaniu: kiedy dodajesz liczbę do obiektu Time, jest ona liczona w sekundach, ale kiedy dodajesz liczbę do DateTime, jest ona liczona w dniach.
Time
nie ma też pojęcia sekund przestępnych, więc nie różni się od DateTime
. Nie wiem, gdzie sprawdziłeś swoje przykłady, ale próbowałem Time.new(2012,6,30,23,59,60,0)
w różnych wersjach Rubiego, od 2.0 do 2.7, i zawsze je otrzymywałem 2012-07-01 00:00:00 +0000
.
Time
obsługuje sekundy przestępne, zależy od konfiguracji systemu operacyjnego i strefy czasowej. Na przykład: TZ=right/UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-06-30 23:59:60 +0000
While TZ=UTC ruby -e 'p Time.new(2012,6,30,23,59,60,0)'
=> 2012-07-01 00:00:00 +0000
.
Myślę, że odpowiedź na „jaka jest różnica” jest jedną z niefortunnych odpowiedzi na to pytanie w standardowych bibliotekach Rubiego: dwie klasy / biblioteki zostały stworzone w różny sposób przez różne osoby w różnym czasie. Jest to jedna z niefortunnych konsekwencji społecznościowej natury ewolucji Ruby w porównaniu do starannie zaplanowanego rozwoju czegoś takiego jak Java. Programiści chcą nowej funkcjonalności, ale nie chcą korzystać z istniejących interfejsów API, więc po prostu tworzą nową klasę - dla użytkownika końcowego nie ma oczywistego powodu, aby istnieć.
Dotyczy to ogólnie bibliotek oprogramowania: często powodem, dla którego jakiś kod lub interfejs API jest taki, że okazuje się on historyczny, a nie logiczny.
Pokusa polega na tym, aby zacząć od DateTime, ponieważ wydaje się bardziej ogólny. Data ... i godzina, prawda? Źle. Czas ma również lepsze daty i może analizować strefy czasowe, w których DateTime nie. Również działa lepiej.
Wszędzie używam Czasu.
Jednak dla bezpieczeństwa zwykle pozwalam na przekazywanie argumentów DateTime do moich interfejsów API Timey i konwersję. Również jeśli wiem, że oba mają metodę, którą jestem zainteresowany, akceptuję albo, jak tę metodę, którą napisałem do konwersji czasów na XML (dla plików XMLTV)
# Will take a date time as a string or as a Time or DateTime object and
# format it appropriately for xmtlv.
# For example, the 22nd of August, 2006 at 20 past midnight in the British Summertime
# timezone (i.e. GMT plus one hour for DST) gives: "20060822002000 +0100"
def self.format_date_time(date_time)
if (date_time.respond_to?(:rfc822)) then
return format_time(date_time)
else
time = Time.parse(date_time.to_s)
return format_time(time)
end
end
# Note must use a Time, not a String, nor a DateTime, nor Date.
# see format_date_time for the more general version
def self.format_time(time)
# The timezone feature of DateTime doesn't work with parsed times for some reason
# and the timezone of Time is verbose like "GMT Daylight Saving Time", so the only
# way I've discovered of getting the timezone in the form "+0100" is to use
# Time.rfc822 and look at the last five chars
return "#{time.strftime( '%Y%m%d%H%M%S' )} #{time.rfc822[-5..-1]}"
end
Time.new(2011, 11, 1, 10, 30)
produkuje 2011-11-01 10:30:00 +0700
podczas DateTime.new(2011, 11, 1, 10, 30)
produkcji Tue, 01 Nov 2011 10:30:00 +0000
.
Odkryłem, że takie funkcje jak analizowanie i obliczanie początku / końca dnia w różnych strefach czasowych są łatwiejsze do wykonania przy użyciu DateTime, przy założeniu , że używasz rozszerzeń ActiveSupport .
W moim przypadku musiałem obliczyć koniec dnia w strefie czasowej użytkownika (dowolnie) na podstawie czasu lokalnego użytkownika, który otrzymałem jako ciąg znaków, np. „2012-10-10 10:10 +0300”
Dzięki DateTime jest to tak proste
irb(main):034:0> DateTime.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 +0300
# it preserved the timezone +0300
Teraz spróbujmy to samo z Time:
irb(main):035:0> Time.parse('2012-10-10 10:10 +0300').end_of_day
=> 2012-10-10 23:59:59 +0000
# the timezone got changed to the server's default UTC (+0000),
# which is not what we want to see here.
Właściwie Time musi znać strefę czasową przed analizą (pamiętaj też, że to Time.zone.parse
nie Time.parse
):
irb(main):044:0> Time.zone = 'EET'
=> "EET"
irb(main):045:0> Time.zone.parse('2012-10-10 10:10 +0300').end_of_day
=> Wed, 10 Oct 2012 23:59:59 EEST +03:00
Tak więc w tym przypadku zdecydowanie łatwiej jest korzystać z DateTime.
DateTime.parse('2014-03-30 01:00:00 +0100').end_of_day
produkuje Sun, 30 Mar 2014 23:59:59 +0100
, ale Time.zone = 'CET'; Time.zone.parse('2014-03-30 01:00:00').end_of_day
produkuje Sun, 30 Mar 2014 23:59:59 CEST +02:00
(CET = + 01: 00, CEST = + 02: 00 - zauważ przesunięcie zmienione). Ale aby to zrobić, potrzebujesz więcej informacji o strefie czasowej użytkownika (nie tylko przesunięcie, ale także to, czy oszczędzasz czas).
Time.zone.parse
jest bardzo przydatne, gdy analizuje się czasy z różnymi strefami - zmusza do zastanowienia się, z której strefy należy korzystać. Czasami Time.find_zone działa jeszcze lepiej.
DateTime
przeanalizował podane przez ciebie przesunięcie, które wynosiło +0100. Nie podałeś Time
przesunięcia, ale strefa czasowa („CET” nie opisuje przesunięcia, to nazwa strefy czasowej). Strefy czasowe mogą mieć różne przesunięcia w ciągu roku, ale przesunięcie jest przesunięciem i zawsze jest takie samo.
Zastanów się, jak inaczej obsługują strefy czasowe w niestandardowych instancjach:
irb(main):001:0> Time.new(2016,9,1)
=> 2016-09-01 00:00:00 -0400
irb(main):002:0> DateTime.new(2016,9,1)
=> Thu, 01 Sep 2016 00:00:00 +0000
irb(main):003:0> Time.new(2016,9,1).to_i
=> 1472702400
irb(main):004:0> DateTime.new(2016,9,1).to_i
=> 1472688000
Może to być trudne przy tworzeniu przedziałów czasowych itp.
Wygląda na to, że w niektórych przypadkach zachowanie jest bardzo różne:
Time.parse("Ends from 28 Jun 2018 12:00 BST").utc.to_s
„2018-06-28 09:00:00 UTC”
Date.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s
„2018-06-27 21:00:00 UTC”
DateTime.parse("Ends from 28 Jun 2018 12:00 BST").to_time.utc.to_s
„2018-06-28 11:00:00 UTC”
Date
(co nie jest zaskakujące) analizuje tylko daty, a kiedy Date
konwertowany jest na Time
, zawsze używa północy w lokalnej strefie czasowej jako godziny. Różnica między Time
i TimeDate
wynika z faktu, że Time
nie rozumie BST, co jest zaskakujące, biorąc pod uwagę, że strefami Time
czasowymi zwykle zarządza się bardziej poprawnie (na przykład w odniesieniu do czasu letniego). Więc w tym przypadku DateTime
poprawnie analizuje tylko cały ciąg.
Oprócz odpowiedzi Nielsa Gansera możesz rozważyć ten argument:
Zwróć uwagę, że Ruby Style Guide dość wyraźnie określa stanowisko w tej kwestii:
Brak daty i godziny
Nie używaj DateTime, chyba że musisz wziąć pod uwagę historyczną reformę kalendarza - a jeśli to zrobisz, wyraźnie określ argument początkowy, aby jasno określić swoje zamiary.
# bad - uses DateTime for current time DateTime.now # good - uses Time for current time Time.now # bad - uses DateTime for modern date DateTime.iso8601('2016-06-29') # good - uses Date for modern date Date.iso8601('2016-06-29') # good - uses DateTime with start argument for historical date DateTime.iso8601('1751-04-23', Date::ENGLAND)