EasyTime - Ruby学习笔记 | 第二章 简单数据(九) 日期计算

来源:百度文库 编辑:神马文学网 时间:2024/10/06 10:52:57

没有任何人知道时间是什么?

Chicago, Chicago IV

人类生活中最复杂,最混乱的就是管理时间。要想完全理解这个主题,你需要学习哲学,天文学,历史,法律,商业,和宗教。天文学家知道(我们大多数都不知道)太阳系时间和恒星时间不是一回事,及为什么一个年终了会偶然地加上闰秒。当意大利从Julian历法转到Gregorian历法时,历史学家知道历法在1582年十月被跳过了几天。很少有人知道天文上的复活日与教会复活日的区别(它们几乎总是一样的)。许多人不知道一世纪内不可以被400除的(如1900年)不是闰年。


历史学家知道历十月1582跳几天,当来自意大利朱利安历的转换果圣歌. 很少人知道区别天文和教会复活节复活节(这几乎都是相同). 许多人穿上 ' t知道世纪的400年没有分割(如1900年)aren ' t年飞跃.

完成对时间和日期的计算是计算机的通常任务,但在大多数语言中,它很乏味。Ruby中它也是乏味,因为日期的本性就是这样。但是Ruby也添加了些东西想使这些操作更容易一些。

我们将给出一些你不太熟悉的术语。这些都来自于其它程序语言的标准用法。

格林尼治标准时间 (GMT)是个旧术语,实际上它不再使用。新的全球标准是协定世界时(UTC)。GMT和UTC实际上是同样的; over a period of years, the difference will be on theorder of seconds. 大多数工业软件根本不区分两者。

夏令时是公务时间,相差在一小时左右。因而美国时区通常用"ST" (标准时间)或 "DT" (夏令时)。大多数国家则不必为此操心。

新纪元是从UNIX借用的术语。在这一领域,时间被典型地以自某一时间点(新纪元)以来的秒数,它从GMT1970年午夜开始。(注:在美国时区,这实际上是1931年12月前。)同样的时间不公可从原始点来表示,也可使用不同的时间点。

Time类有很多操作。Date和ParseDate库扩展了它的能力。我们将看一下它的基本技术和它能为我们解决什么问题。

1、判断当前时间

 

在日期/时间操作上的大多数问题是要回答这个问题:现在时间和日期是什么?在Ruby中,当我们不带参数创建一个Time对象时,它被设置为当前的日期和时间。

t0 = Time.new

Time.now是同义词

t0 = Time.now

注意,这由系统时钟的体系来决定的。它可能包括微秒;在这此情况下,两个连续创建的Time对象实际上可能记录不同的时间。

2、Working with Specific Times (Post-epoch)

 

大多数软件在未来或当前只需要用日期工作。对于这些情况,Time类是足够的。相应的类方法是mtime,local,gm,和utc。

mktime方法将基于传递给它的参数创建一个新的Time对象。这些给出的时间单元从长到短:year, month, day,hours, minutes, seconds, microseconds。但年是可选的;它们的缺省值是最小值。在大多数体系中微秒可以被忽略。小时必须在0和23之间。

t1 =Time.mktime(2001) # January 1,2001 at 0:00:00

t2 = Time.mktime(2001,3)

t3 =Time.mktime(2001,3,15)

t4 =Time.mktime(2001,3,15,21)

t5 =Time.mktime(2001,3,15,21,30)

t6 =Time.mktime(2001,3,15,21,30,15) # March 15, 20019:30:15 pm

注意mktime假设为本地时区。事实上,Time.local是它的义词。

t7 =Time.local(2001,3,15,21,30,15) # March 15, 20019:30:15 pm

Time.gm方法基本上是一样的,除了它假设使用GMT (或UTC)。因为作者是使用美国中部标准时间,我们在这儿会看到八小时的差别。

t8 = Time.gm(2001,3,15,21,30,15) # March 15, 2001 9:30:15 pm

#This is only 1:30:15 pm in Central time!

Time.utc方法是同义词。

t9 =Time.utc(2001,3,15,21,30,15) # March 15, 20019:30:15 pm

# Again, 1:30:15 pm Central time.

这儿有个更重要的条目要注意。所有这些方法都接受一个可选的参数集。实例方法to_a(它转换时间到一个有相应值的数组中)返回这个次序的值: seconds, minutes, hours,day, month, year, 星期几(0..6), 一年的第几天(1..366), 有无夏令时 (true or false), 和时区 (字符串)。

因此,这些也是有效的调用:

t0 = Time.local(0,15,3,20,11,1979,2,324,false,"GMT-8:00")

t1 =Time.gm(*Time.now.to_a)

然而,第一个例子中,不要掉入这样的思维陷井,你可以更改可计算的参数,如星期几(这种情况下,2意味着星期三)。像这样的修改同我们日历工作的方式冲突,在创建时间对象时它没有影响。November20, 1979, 是星期三而不会理会我们何时写的代码。

最后,注意这儿有多种方式试图编码不正确的时间,如,第十三月,月的第三十五天。像这种情况,会引发ArgumentError错误。

3、确定周的第几天

 

有几种方式可以做到。首先,实例方法to_a将返回时间信息的数组。你可以访问第七个元素,这的数值是从0到6(0是星期日6是星期六)。

time =Time.now

day = time.to_a[6] # 2(meaning Tuesday)

最好是使用实例方法wday:

day = time.wday # 2(meaning Tuesday)

但是这两种技术有些笨重。有时候,我们想将值编码为数字,但更多时候却不是。要获取字符串样的实际星期数值,我们可以使用strftime方法。这个名字对C程序可能很熟悉。它有几近22个的不同指示符,使我们可以在更多或更少的地方格式化日期和时间。

day = time.strftime("%a") # "Tuesday"

也可以获得一个简称。

long = time.strftime("%A") #"Tuesday"

4、复活节日期确定

 

传统上,这个假日是最难计算的一个,因为它与月亮的周期有关。朔望月并不是平均地出现在回归年,因而基于月亮的任何事都可以年复一年的期待。

Thealgorithm we present here is a well-known one that has made the rounds. 我们在BASIC, Pascal,和C中都看到它的代码。现在我们把Ruby代码呈现给你。

defeaster(year)

c = year/100

n = year - 19*(year/19)

k= (c-17)/25

i = c - c/4 - (c-k)/3 + 19*n + 15

i = i - 30*(i/30)

i =i - (i/28)*(1 -(i/28)*(29/(i+1))*((21-n)/11))

j = year + year/4 + i + 2 - c + c/4

j = j - 7*(j/7)

l =i - j

month = 3 + (l+40)/44

day = l + 28 - 31*(month/4)

[month, day]

end

date =easter 2001 # Find month/day for 2001

date =[2001] + date # Tack year on front

t =Time.local *date # Pass parameters toTime.local

puts t # Sun Apr 1501:00:00 GMT-8:00 2001

我们想给你解释这个算法,但我们自己也不理解它。有些事情必须接受信念,像Easter这种情况,这可能是特别合适的。

5、查找月内的第几周

 

有时候对给出的月和年,我们想找出月内的第三个星期一是哪一天,或者是第二个星期二等等。Listing2.9给出了简单的计算。

如果我们想查看确定周的第几次出现,我们传递n做为第一个参数。第二个以数是周的星期几数(0是星期天,1是星期一等等)。第三和第四个参数分别是月和年。

Listing 2.9 Finding the Nth Weekday

 

defnth_wday(n, wday, month, year)

if(!n.between? 1,5) or

(!wday.between?0,6) or

(!month.between? 1,12)

raise ArgumentError

end

t = Time.local year, month, 1

first = t.wday

iffirst == wday

fwd = 1

elsif first < wday

fwd= wday - first + 1

elsif first > wday

fwd = (wday+7) - first + 1

end

target= fwd + (n-1)*7

begin

t2 = Time.local year, month, target

rescue ArgumentError

returnnil

end

ift2.mday == target

t2

else

nil

end

end

putsnth_wday(ARGV[0].to_i, ARGV[1].to_i, ARGV[2].to_i, ARGV[3].to_i)

方法尾部的独特的代码是在基本的时间处理事务内放置连接的长传统标准。 你可能试图创建November 31的数据这将引起一些种类的错误。你是在犯错误。大多数系统愉快地(或默默地)转换到December 1。如果你是电脑黑客,你可能认为这一个特征;否则,你可能认为这是一个bug。

这儿我们没有冒险提出这样评价,关于基础库代码应该做或Ruby是否应该修改那个行为。但是我们不想有这样的程序。如果你看了下日期说,2000年11月15号星期五,你会获得一个返回的nil值(而不是2000年12月1日)。

6、在秒和大单元之间转换

 

有时候,我们想接受许多秒,然后转换成天,小时,分钟,和秒。这个小程序将做这些。

def sec2dhms(secs)

time = secs.round # Getrid of microseconds

sec = time % 60 # Extract seconds

time /= 60 # Getrid of seconds

mins = time % 60 # Extract minutes

time /= 60 # Getrid of minutes

hrs = time % 24 # Extract hours

time /= 24 # Getrid of hours

days = time# Days (final remainder)

[days,hrs, mins, sec] # Return array [d,h,m,s]

end

t =sec2dhms(1000000) # A million seconds is...

puts"#{ t[0]} days," #11 days,

puts "#{ t[1]} hours," # 13 hours,

puts "#{ t[2]} minutes," # 46 minutes,

puts "and #{ t[3]} seconds." # and 40 seconds.

当然,我们可以得到更高的单元,但是周不应是被过度使用的单元,月也不是定义良好的术语,年很少是来自许多整数的天。

我们在这儿也提出了一个相反的函数。

def dhms2sec(days,hrs=0,min=0,sec=0)

days*86400 + hrs*3600 + min*60 + sec

end

7、在新纪元前后转换

 

由于各种原因,我们可能想在内部(或传统上的)测量和标准日期形式之间来回转换。在内部,日期被存储为至新纪元以来的许多秒。

Time.at类方法将用给出的至新纪元以来的秒数创建新时间。

epoch = Time.at(0) # Findthe epoch (1 Jan 1970 GMT)

newmil =Time.at(978307200) # Happy New Millennium! (1 Jan 2001)

相反的实例方法是to_i,它转换到一个整数。

now = Time.now #16 Nov 2000 17:24:28

sec = now.to_i # 974424268

如果你需要微秒,并且你操作系统支持,你可以使用to_f来转换到一个浮点数值。

8、Working with LeapSeconds: Don't!

 

Ah, but mycalculations, people say,

Reduced the year tobetter reckoning? Nay,

'Twas only strikingfrom the calendar

Unborn Tomorrow and dead Yesterday.

Omar Khayyam, The Rubaiyat(translation by Fitzgerald)

你希望用闰秒工作?我们的忠告是:不要这样做。

Although leap seconds are very real and for years thelibrary routines have for years allowed for the possibility of a61-second minute, our experience has been that most systems don't keeptrack of leap seconds. By most, we mean all the ones we've ever checked.

例如,闰秒已经知道被插入在1998年的最后一天。23时59分59秒之间立即跟随23时59分60秒。但是建立在基础的C语言库上的Ruby忽略这些。

t0 =Time.gm(1998, 12, 31, 23, 59, 59)

t1 = t0 + 1

puts t1 # Fri Jan 01 00:00:00 GMT 1999

可以想像得到,Ruby添加了一层来修正这一点。在写此书时,还没有打算添加些功能。

9、查找是一年内的第几天

 

年内的天数有时候被称为Julian日期,它与Julian历法没有直接联系。有些人认为这个用法不是正确的,所以我们从这儿开始就不使用它。

无论什么事调用它,这儿会有你想知道的该年的第多少天的时间,从1到366。在Ruby中这很容易;我们使用yday方法。

t =Time.now

day = t.yday # 315

10、检验日期/时间

 

就像你在"Findingthe Nth Weekday in a Month"中看到的,标准的日期/时间函数不检查日期的有效性,但会按需要滚动它。例如,11月31日将被转译成12月1日

有时候,这可能是你想要的行为。如果不是,你将乐于知道,标准库Date没有保证对日期进行检验。我们可以使用这个事实来完成日期的检验,像我们的例子。

classTime

def Time.validate(year, month=1, day=1,

hour=0, min=0, sec=0, usec=0)

require "date"

begin

d = Date.new(year,month,day)

rescue

returnnil

end

Time.local(year,month,day,hour,min,sec,usec)

end

end

t1 =Time.validate(2000,11,30) # Instantiates a validobject

t2 = Time.