Программирование на языке Ruby - Хэл Фултон
Шрифт:
Интервал:
Закладка:
def sec2dhms(seсs)
time = seсs.round # Отбрасываем микросекунды.
sec = time % 60 # Извлекаем секунды.
time /= 60 # Отбрасываем секунды.
mins = time % 60 # Извлекаем минуты.
time /= 60 # Отбрасываем минуты.
hrs = time % 24 # Извлекаем часы.
time /= 24 # Отбрасываем часы.
days = time # Дни (последний остаток).
[days, hrs, mins, sec] # Возвращаем массив [d,h,m,s].
end
t = sec2dhms(1000000) # Миллион секунд равно...
puts "#{t[0]} days," # 11 дней,
puts "#{t[1]} hours," # 13 часов,
puts "#{t[2]} minutes," # 46 минут
puts " and #{t[3]} seconds." # и 40 секунд.
Можно было пойти и дальше. Но неделю вряд ли назовешь полезной единицей, месяц не слишком точно определен, а год не всегда содержит одно и то же число дней.
Ниже приведена также обратная функция:
def dhms2sec(days,hrs=0,min=0,sec=0)
days*86400 + hrs*3600 + min*60 + sec
end
7.7. Вычисление промежутка времени, прошедшего от точки отсчета
По разным причинам может понадобиться перейти от внутреннего (традиционного) представления времени к стандартному. В системе время хранится как число секунд, прошедших с точки отсчета.
Метод класса Time.at создает новый объект Time, зная, сколько секунд прошло с точки отсчета:
epoch = Time.at(0) # Найти точку отсчета (1 января 1970 GMT)
newmil = Time.at(978307200) # Счастливого миллениума! (1 января 2001)
Обратная функция — это метод экземпляра to_i, который преобразует дату в целое число.
now = Time.now # 16 Nov 2000 17:24:28
sec = now.to_i # 974424268
Если нужны микросекунды, и система поддерживает такую точность, то можно воспользоваться методом to_f для преобразования в число с плавающей точкой.
7.8. Високосные секунды
Вот снова день исчез, как ветра легкий стон,Из нашей жизни, друг, навеки выпал он.Но я, покуда жив, тревожиться не стануО дне, что отошел, и дне, что не рожден.[10]
Омар Хайям, «Рубаи»Хотите иметь дело с «високосными» секундами? Мой совет: не делайте этого.
Високосные секунды — это не миф. Одна была добавлена в 2005 году; его последняя минута состояла из 61 секунды, а не из 60. Библиотечные функции уже много лет учитывают возможность появления минут, состоящих из 61 секунды. Но наш опыт показывает, что большинство операционных систем високосные секунды игнорирует. Говоря «большинство», мы имеем в виду все, с которыми когда-либо сталкивались.
Известно, например, что високосная секунда была добавлена в конец последнего дня 1998 года. Вслед за моментом 23:59:59 наступил редкий момент 23:59:60. Но стандартная библиотека языка С, которой пользуется Ruby, этого в расчет не принимает.
t0 = Time.gm(1998, 12, 31, 23, 59, 59)
t1 = t0 + 1
puts t1 # Fri Jan 01 00:00:00 GMT 1999
Быть может (хотя и маловероятно), Ruby скорректирует эту ситуацию. Но во время работы над данной книгой таких планов не было.
7.9. Определение порядкового номера дня в году
Порядковый номер дня в году иногда еще называют юлианской датой, хотя это не имеет прямого отношения к юлианскому календарю, давно вышедшему из употребления. Многие считают, что такое название неправильно, поэтому мы им больше пользоваться не будем.
Но как ни называй порядковый номер дня, иногда хочется его узнать, то есть получить число от 1 до 366. В Ruby это просто — достаточно вызвать метод yday:
t = Time.now
day = t.yday # 315
7.10. Контроль даты и времени
В разделе 7.5 было показано, что стандартные функции не проверяют корректность даты, а «переносят» ее вперед, если необходимо. Например, 31 ноября становится 1 декабря.
Иногда такое поведение даже желательно. А если нет, то спешу обрадовать: стандартная библиотека Date не считает такие даты правильными. Мы можем воспользоваться этим фактом для контроля переданной даты.
class Time
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
return nil
end
Time.local(year,month,day,hour,min,sec,usec)
end
end
t1 = Time.validate(2000,11,30) # Создается корректный объект.
t2 = Time.validate(2000,11,31) # Возвращается nil.
Здесь не мудрствуя лукаво мы просто возвращаем nil, если переданные параметры не соответствуют правильной дате (полагаясь на вердикт, вынесенный классом Date). Мы оформили этот метод как метод класса Time по аналогии с другими методами создания объектов.
Отметим, что класс Date может работать и с датами, предшествующими точке отсчета, то есть дата 31 мая 1961 года с точки зрения этого класса вполне допустима. Но при попытке передать такие значения классу Time возникнет исключение ArgumentError. Мы не пытаемся его перехватить, полагая, что это лучше делать на том же уровне пользовательского кода, где обрабатывались бы исключения, скажем, от метода Time.local.
Раз уж зашла речь о Time.local, то отметим, что мы воспользовались именно этим методом. Захоти мы работать со временем по Гринвичу, нужно было бы вызывать метод gmt. Лучше реализовать оба варианта.
7.11. Определение недели в году
Что такое «порядковый номер недели», не вполне ясно. Разные компании, коалиции, правительственные учреждения и органы стандартизации по-разному определяют это понятие. Путаница связана с тем, что год может начинаться с любого дня недели. Все зависит от того, хотим ли мы учитывать неполные недели. К тому же в одних странах неделя начинается с воскресенья, в других — с понедельника.
В этом разделе мы предложим три варианта. Первые два основаны на методе strftime класса Time. Спецификатор %U отсчитывает недели, начинающиеся с воскресенья, а спецификатор %W — начинающиеся с понедельника.
Третью возможность предоставляет класс Date. В нем имеется метод доступа cweek, который возвращает порядковый номер недели, следуя определению из стандарта ISO 8601 (согласно которому первой считается неделя, содержащая первый вторник года).
Если все это вам не подходит, можете придумать собственный алгоритм. Все три решения включены в один фрагмент кода:
require "date"
# Посмотрим, в какую неделю попадает 1 мая в 2002 и 2005 годах.
t1 = Time.local(2002,5,1)
d1 = Date.new(2002,5,1)
week1a = t1.strftime("%U").to_i # 17
week1b = t1.strftime("%W").to_i # 17
week1c = d1.cweek #18
t2 = Time.local(2005,5,1)
d2 = Date.new(2005,5,1)
week2a = t2.strftime("%U").to_i # 18
week2b = t2.strftime("%W").to_i # 18
week2c = d2.cweek # 17
7.12. Проверка года на високосность
В классе Date есть два метода класса julian_leap? и gregorian_leap?, но только последний применим к относительно недавнему времени. Есть также метод leap?, но это просто синоним gregorian_leap?.
require "date"
flag1 = Date.julian_leap? 1700 # true
flag2 = Date.gregorian_leap? 1700 # false
flag3 = Date.leap? 1700 # false
Любой ребенок знает первое правило проверки на високосность: год должен делиться на 4. Меньшее число людей знают второе правило: год не должен делиться на 100. И уж совсем немногие знают про исключение из второго правила: если год делится на 400, то он високосный. Таким образом, последний год тысячелетия является високосным, только если делится на 400; так, 1900 год не был високосным, а 2000 был. (Эта поправка необходима, потому что в году не ровно 365.25 дней, а приблизительно 365.2422.)
В классе Time нет аналогичного метода, но при желании его легко можно добавить.
class Time
def Time.leap? Year
if year % 400 == 0
true
elsif year % 100 == 0
false
elsif year % 4 == 0
true
else
false
end
end
Я привел этот код только для того, чтобы прояснить алгоритм; конечно, гораздо проще вызвать метод Date.leap?. В моей реализации это метод класса по аналогии с классом Date, но можно было бы сделать его и методом экземпляра.
7.13. Определение часового пояса
Метод zone класса Time возвращает название часового пояса в виде строки:
z1 = Time.gm(2000,11,10,22,5,0).zone # "GMT-6:00"
z2 = Time.local(2000,11,10,22,5,0).zone # "GMT-6:00"