2008-10-26 13 views
39

Mam Rubinowy DateTime, który zostanie wypełniony z formularza. Dodatkowo mam również n godzin od formularza. Chciałbym odjąć te n godzin od poprzedniego DateTime. (Aby uzyskać przedział czasowy).Odejmij n godzin od DateTime w Ruby

DateTime ma dwie metody "-" i "< <", aby odjąć dzień i miesiąc, ale nie godzinę. (API). Wszelkie sugestie, jak mogę to zrobić?

Odpowiedz

46

Możesz to zrobić.

adjusted_datetime = (datetime_from_form.to_time - n.hours).to_datetime 
+42

Należy pamiętać, że działa to tylko w kontekście Rails. Metoda hours() pochodzi od jednego z pomocników daty w Railsach, nie od standardowej biblioteki języka Ruby. –

+43

To frustrujące, jak wiele osób myśli, że Ruby = Railsy –

3

DateTime nie może tego zrobić, ale Time może:

t = Time.now 
t = t-hours*60 

Zauważ, że Time również przechowuje aktualne informacje, to wszystko jest trochę dziwne.

Jeśli trzeba pracować z DateTime

DateTime.commercial(date.year,date.month,date.day,date.hour-x,date.minute,date.second) 

może działać, ale jest brzydki. Doc mówi DateTime jest niezmienne, więc nie jestem nawet pewien - i <<

+0

Tak, "Obiekty DateTime są niezmienne po utworzeniu." - to jest jak klasa String w Javie. Niemniej jednak: ** metoda "-": ** "Jeśli x jest wartością numeryczną, utwórz nowy obiekt Date, który jest x dni wcześniejszy niż bieżący.", Abyśmy mogli z tym pracować. Pomyślałem także o twoim pomyśle, ale nie jestem pewien, w jaki sposób ruby ​​konwertuje wartość ujemną z "godziny-x". Czy to jest dla ciebie brzydkie? Cóż, próbuję tego. Zobaczmy, co robi klasa DateTime;) –

3

EDIT: Spójrz na this question zanim zdecydujesz się na stosowanie metody Mam opisaną tutaj. Wydaje się, że nie jest najlepszą praktyką modyfikowanie zachowania klasy bazowej w Ruby (co mogę zrozumieć). Więc, weź tę odpowiedź z przymrużeniem oka ...


MattW's answer był pierwszą rzeczą, myślałem, ale ja też nie lubię go bardzo dużo.

Przypuszczam, że można uczynić go mniej brzydki przez łatanie DateTime i Fixnum robić to, co chcesz:

require 'date' 

# A placeholder class for holding a set number of hours. 
# Used so we can know when to change the behavior 
# of DateTime#-() by recognizing when hours are explicitly passed in. 

class Hours 
    attr_reader :value 

    def initialize(value) 
     @value = value 
    end 
end 

# Patch the #-() method to handle subtracting hours 
# in addition to what it normally does 

class DateTime 

    alias old_subtract - 

    def -(x) 
     case x 
     when Hours; return DateTime.new(year, month, day, hour-x.value, min, sec) 
     else;  return self.old_subtract(x) 
     end 
    end 

end 

# Add an #hours attribute to Fixnum that returns an Hours object. 
# This is for syntactic sugar, allowing you to write "someDate - 4.hours" for example 

class Fixnum 
    def hours 
     Hours.new(self) 
    end 
end 

Następnie można napisać kod tak:

some_date = some_date - n.hours 

gdzie n jest liczba godzin, które chcesz odjąć od some_date

+0

To jest kompletne rozwiązanie. Nie wiem, czy rzeczywiście zadał pytanie na ten temat, czy też nie odkrył, że nowy operator to "Ujemne wartości h, min i sec traktowane są jak odliczanie wstecz od końca następnej większej jednostki" – Jonke

3

Nie powiedziałeś, jakiego użycia potrzebujesz do wykonania va Jeśli masz, ale co z dzieleniem godzin przez 24, więc odejmujesz ułamek dnia?

mydatetime = DateTime.parse(formvalue) 
nhoursbefore = mydatetime - n/24.0 
2

Lubię korzystać z pomocy w active_support. Dzięki temu jest naprawdę czysty i łatwy do odczytania.

patrz przykład poniżej:

require 'active_support' 

last_accessed = 2.hours.ago 
last_accessed = 2.weeks.ago 
last_accessed = 1.days.ago 

Nie może być sposobem korzystania z tego rodzaju składni zrobić to, czego szukasz, jeśli używana jest bieżąca data.

26

Można po prostu odjąć mniej niż jeden cały dzień:

two_hours_ago = DateTime.now - (2/24.0) 

To działa na minuty i cokolwiek innego TOO:

hours = 10 
minutes = 5 
seconds = 64 

hours = DateTime.now - (hours/24.0) #<DateTime: 2015-03-11T07:27:17+02:00 ((2457093j,19637s,608393383n),+7200s,2299161j)> 
minutes = DateTime.now - (minutes/1440.0) #<DateTime: 2015-03-11T17:22:17+02:00 ((2457093j,55337s,614303598n),+7200s,2299161j)> 
seconds = DateTime.now - (seconds/86400.0) #<DateTime: 2015-03-11T17:26:14+02:00 ((2457093j,55574s,785701811n),+7200s,2299161j)> 

Jeśli zmiennoprzecinkowych arytmetycznych nieścisłości są problemem, można wykorzystać Rational lub inne bezpieczne narzędzie arytmetyczne.

+3

Uwaga, prowadzi to do nieoczekiwanych rezultatów z powodu niedokładności arytmetyki zmiennoprzecinkowej ... – JtR

+2

W takim przypadku klasa Rational jest rozwiązaniem ^^ – Smar

10

n/24.0 sztuczka nie zadziała prawidłowo jako pływaki są ostatecznie zaokrąglone: ​​

>> DateTime.parse('2009-06-04 02:00:00').step(DateTime.parse('2009-06-04 05:00:00'),1.0/24){|d| puts d} 
2009-06-04T02:00:00+00:00 
2009-06-04T03:00:00+00:00 
2009-06-04T03:59:59+00:00 
2009-06-04T04:59:59+00:00 

Można jednak użyć klasy Rational Zamiast:

>> DateTime.parse('2009-06-04 02:00:00').step(DateTime.parse('2009-06-04 05:00:00'),Rational(1,24)){|d| puts d} 
2009-06-04T02:00:00+00:00 
2009-06-04T03:00:00+00:00 
2009-06-04T04:00:00+00:00 
2009-06-04T05:00:00+00:00 
+5

Również nie jest rdzeniem Ruby. – dfrankow

+1

Miałem na myśli: Wierzę, że "krok" nie jest rdzeniem Rubiego. Można jednak strategię Rational z odejmowaniem prostym, jak w niektórych innych odpowiedziach. – dfrankow

+3

1) 'step' był tam tylko dla zilustrowania punktu 2)' step' jest w standardowej wersji lib: http://rubydoc.info/stdlib/date/1.9.3/Date:step –

20

Sposób zaliczka jest miłe, jeśli chcesz wyraźniej o takich zachowaniach.

adjusted = time_from_form.advance(:hours => -n) 
+21

Dla każdego, kto próbuje tego: nie jest na stdlib, jest dostarczany przez ActiveSupport. Zobacz tutaj: http://stackoverflow.com/questions/617284/where-is-time-advance-documented. – jkp

+2

Dzięki jkp. Robiłem się sfrustrowany tymi wszystkimi przykładami, które nie działały w zwykłym irb. – swilliams

-1

Można to wykorzystać:

Time.now.ago(n*60*60) 

Na przykład Time.now.ago(7200) poda datę i czas, który był przed 2 godziny od teraz.

+6

To pytanie dotyczy Ruby, a nie Ruby on Rails. – Mischa

10

Wystarczy zdjąć frakcje dziennie.

two_hours_ago = DateTime.now - (2.0/24) 
  • 1,0 = jeden dzień
  • 1,0/24 = 1 godzina
  • 1,0/(24 * 60) = 1 minuta
  • 1,0/(24 * 60 * 60) = 1 sekunda