碎碎念
Ruby 的時間計算功能很強的, 而且 Rails 再新增更多的功能, 像 1.day.from_now 就可以給你一天後的時間。 這更能是別的語言比較少看到的。 要測試本文章裡的時間功能不要忘了開啟 script/console 才試驗喔, 因為有些時間的功能是 Rails 外加的, Ruby 裡沒有。
本文章的範圍
- 加/減時間
- 時間顯示的格式
- 跟時間有關的欄位命名
- Time 跟 DateTime 的差別
- 大約的時間
- 精準的時間
- 去資料庫撈某某時間內的資料
- 資料分成一天一天來顯示
不會討論到:
快速小抄
注意: 單數或複數並不會有差別, 1.days 跟 1.day 是一樣的。 5.day 跟 5.days 也是一樣的。
# 計算時間前有時候會需要用到時間的 class
require 'date'
# 可用 seconds, minutes, days, years
4.seconds.from_now # 4 秒鐘之後
(4.years + 5.seconds).from_now # 9 秒鐘之後
4.days.ago # 4 天前
Time.now + (60 * 60 * 24) # 一天後; 跟 1.day.from_now 是一樣的
# 電影會在 less than 20 seconds 後開始
電影會在 <%= time_ago_in_words(showtime.start_at)%> 後開始
# 嗯,這個弄成中文有夠怪
# 這個專案花了 about 5 hours
這個專案花了 <%= distance_of_time_in_words(project.start_at, project.end_at)%>
# 電影長度: 2 小時 14 分
diff = showtime.end_at.to_datetime - showtime.start_at.to_datetime
hours, mins, secs, ignore_fractions = Date::day_fraction_to_time(diff)
電影長度: <%= hours.to_s %> 小時 <%= mins.to_s %> 分
# 也可用成 end_of_day, end_of_week ..... month, quarter, year 等等
Time.now.end_of_month #Tue Sep 30 23:59:59 +0800 2008 <- 這月底
Time.now.beginning_of_day
# 電影週: 8/24
電影週: <%= showtime.start_at.beginning_of_week.strftime("%m/%d") %>
時間顯示的格式
有 Time 的物件, 時間要怎麼顯示都可以, 只需加上 .strftime
# 中原標準時間14點22分整
Time.now.strftime("中原標準時間%H點%M分整")
%a - 星期一..星期日的簡寫 (Sun) %A - 星期一..星期日 (Sunday) %b - 月的簡寫 (Jan) %B - 月 (January) %c - 時間顯示成當地的格式 (這個做什麼我還沒試) %d - 月裡的幾號 (01..31) %H - 時, 軍隊常用的 24 小時 (00..23) %I - 時, 12 小時 (01..12) %j - 年裡的第幾天 (001..366) %m - 月 (01..12) %M - 分 (00..59) %p - AM 或 PM %S - 秒 (00..60) %U - 這一年的第幾個禮拜,這一年的第一個星期日就是第一個禮拜的開始 (00..53) %W - 這一年的第幾個禮拜,這一年的第一個星期一就是第一個禮拜的開始(00..53) %w - 週裡的第幾天 (星期天是 0, 0..6) %x - 時間顯示成當地的格式,只有顯示日期,沒有顯示時間 %X - 時間顯示成當地的格式,只有顯示時間,沒有顯示日期 %y - 年,沒顯示前兩碼,像 08,不是 2008 (00..99) %Y - 年 %Z - 時區的名字 %% - %
Rails 欄位命名習慣
在資料表裡,date 屬性的欄位名字後面要加 _on. Timestamp 屬性的欄位名字要加 _at。 例如, “last_edited_on” 是個 date 欄位, “shipped_at” 是個 timestamp 的欄位。
Time 跟 DateTime 的差別
在這頁的範例裡, 我們會用一個叫 showtime 的 model. Showtime 裡有幾個欄位,其中兩個是 start_at 和 end_at。 當初我用 migrations 來建立 showtimes 的資料表時, 我有指定欄位的屬性要用 datetime。 可是 Rails 從 MySQL 資料庫撈出資料時, 資料的屬性會是 Time, 不會是 Dateime。 這點要記著, 之後有用到 Time 或 DateTime 的 methods 時才不會搞混。 下面有段 “電影長度: 2 小時 14 分” 有講到 Time 該換成 DateTime 的情況。
下面的圖片顯示著 Time 變成 DateTime 的方式:

電影會在 less than 20 seconds 後開始
要把現在的時間跟另一個時間比的話,可以用 time_ago_in_words
電影會在 <%= time_ago_in_words(showtime.start_at)%> 後開始
得到的結果會類似 “電影會在 less than 20 seconds 後開始”。 這個結果對中文介面是很怪的, 不知道目前有沒有辦法把語言在地化。 如沒辦法的話, Rails 2.2 會出來個 Rails 的國際化, 看看那時有沒有解。
這個專案花了 about 5 hours
這個專案花了 <%= distance_of_time_in_words(project.start_at, project.end_at)%>
這段碼會顯示 “這個專案花了 about 5 hours”。 你可以給 distance_of_time_in_words 兩個時間來比對。 time_ago_in_words 的話就只有比對現在的時間跟你給它的時間點。
電影長度: 2 小時 14 分
找出兩個時間點中精準的長度:
<% diff = showtime.end_at.to_datetime - showtime.start_at.to_datetime %> <% hours, mins, secs, ignore_fractions = Date::day_fraction_to_time(diff)%> 電影長度: <%= hours.to_s %> 小時 14 <%= mins.to_s %> 分
我們用 Date::day_fraction_to_time 來計算兩個時間點中的長度。 “Date::” 這部份是表示 day_fraction_to_time 的 method 是在 Date 的 class 裡的。 這樣的話此 class 的開頭就不需用 require ‘date’ 來匯入 Date 這個 class。 day_fraction_to_time 只接受 Rational class 的物件。 我們如果直接用資料庫撈出來的時間來互減的話, 得到的答案會是 Float class, 不是 Rational class, 所以我們要先把時間改成 DateTime 再來減:

在本頁的大部分計算都不用換成 DateTime。 我們要換是因為要用到 Date::day_fraction_to_time 的功能。 寫程式時如果得到的答案不是預期的話, 就先看看是否用的 class 是正確的。 要看看用的是什麼 class 很簡單, 下這個指令就可以了: some_object.class
下面這行碼對沒看過的人看起來一定很奇怪, 我第一次看到的反應是 驚!啥?
hours, mins, secs, ignore_fractions = Date::day_fraction_to_time(diff)
這是在做啥呢? 基本上, day_fraction_to_time(diff) 計算完後會給你四個答案, 這四個答案就存在左邊的四個變數: hours, mins, secs, ignore_fractions。 這四個變數就是 小時, 分鐘, 秒, 還有計算剩餘的一些可忽略的數字。 你也可以自己亂取名:
monkeys, lions, ants, chipmonks = Date::day_fraction_to_time(diff)
可是取這樣的話, 你的程式碼就會變成:
電影長度: <%= monkeys.to_s %> 小時 <%= lions.to_s %> 分
所以還是用原來的 hours, min, secs 好了。
電影週: 8/24
如果你想要知道月底是幾號, 或是某天的最後一時刻, 你可以用 end_of_month 或 end_of_day. 要查最前端的時間也可以, 用 beginning_of_week.
Time.now.end_of_month # 也可用 day, week, quarter, year
電腦回覆的答案會是:
Tue Sep 30 23:59:59 +0800 2008
另一個範列:
電影週: <%= showtime.start_at.beginning_of_week.strftime("%m/%d") %>
就會顯示 電影週: 8/24
去資料庫撈某某時間內的資料
在撈資料時 Order.find(:all, :conditions => [])
:conditions 裡面的條件可寫成:
:conditions => ["start_at >= :one_day and start_at < :another_day",
{:one_day => Time.now, :another_day => Time.now + 4.days }]
或者也可以用 “between”
:conditions => ["start_at between :one_day and :another_day",
{:one_day => Time.now, :another_day => Time.now + 4.days }]
如果有更好的方法再跟我說一下。
資料分成一天一天來顯示
有時候顯示資料時, 我希望把同一天的資料放在一起。 例如, 我的電視節目表要把星期五的節目放在一起, 星期六的節目放在一起, 依此類推。 使用者看到的網頁應該是:
07/11 Fri Desparate Housewives from 06:00 PM to 08:00 PM Ugly Betty from 07:00 PM to 08:00 PM Startrek Voyager from 10:00 PM to 11:00 PM 07/12 Sat X-men from 04:00 PM to 06:00 PM Total Recall from 06:00 PM to 08:00 PM 07/13 Sun Superman from 06:00 PM to 08:00 PM Babylon 5 from 07:00 PM to 08:00 PM Fatstic Four from 11:00 PM to 12:00 AM
要顯示這些資料的話, controller 跟 view 都要改過。 首先把 controller 改成:
def index
@showtimes = Showtime.find(:all, :order => "start_at" )
@showtimes_by_day = @showtimes.group_by { |t| t.start_at.beginning_of_day }
end
所有的節目撈出來後存在 @showtimes 的變數裡。 這時候我們要找出一個共同點來結合每天上映的節目。 同一天上映的節目的共同點就是那一天的最早的一時刻, 也就是每個節目的 beginning_of_day。 group_by 的條件就是 beginning_of_day 了。 節目分組好了就存放在 @showtimes_by_day。 @showtimes_by_day 可以在 view 裡如此使用:
<% @showtimes_by_day.each do |day, showtimes| %>
<b><%= day.strftime("%m/%d %a") %> </b>
<% for showtime in showtimes %>
<p>
<%=h showtime.show.title %> from <%= showtime.start_at.strftime("%I:%M %p") %> to
<%= showtime.end_at.strftime("%I:%M %p") %>
</p>
<% end %>
<% end %>
@showtimes_by_day.each 給的就是每一組的節目。 同天的節目是納在同組的。 在同一組裡我們有兩個變數可用, 一個是 day, 另一個是 showtimes。 day 包含的是節目上映的那一天, 所以用 day.strftime 就能顯示那天的時間。 showtimes 包含的就是那一天上映的所有節目。 裡面的資料顯示方法跟平常的沒啥兩樣。