Oct 7
時間
icon1 admin | icon2 Uncategorized | icon4 10 7th, 2008| icon3No Comments »

碎碎念

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 包含的就是那一天上映的所有節目。 裡面的資料顯示方法跟平常的沒啥兩樣。

« Previous Entries Next Entries »


 | Powered by Wordpress Themes

Copyright © 本網站由臺灣國家高速網路與計算中心 格網技術組 贊助
2008 :)