Data.Time.Calendar で日付の計算をする。

Data.Time.Calendar を使って日付の計算をしてみます。

  • Data.Time.Calendar の日付は1858-11-17を0とした修正ユリウス日により管理されています。

数年にわたる2点の日数を計算するのには、紀元前4713年1月1日正午からの日数であるユリウス日に変換して計算すると便利なため、天文学ではユリウス日が使われるそうです。ユリウス日では桁が多すぎるため、ユリウス通日から2400000.5を引いたのが修正ユリウス日(MJD)(1858年11月17日0000UT元期)です。(wikipedia)

ghci> :m + Data.Time.Calendar
-- Day型のデータを西暦から作ります。
ghci> fromGregorian 1858 11 17          -- => 1858-11-17
  • Day型は newtype で定義された Integer の値です。
  • show instance が "yyyy-mm-dd" の文字列に変換します。実態はIntegerでも表示されるときは"yyyy-mm-dd"のスタイルになります。
> :i Day
-- => newtype Day = ModifiedJulianDay {toModifiedJulianDay :: Integer}
  • 0 のModifiedJulianDayを作ると修正ユリウス日の基準日が作られ、そのshow instance を表示します。
ghci> ModifiedJulianDay {toModifiedJulianDay=0}     -- => 1858-11-17
ghci> ModifiedJulianDay {toModifiedJulianDay=55807} -- => 2011-09-03

ghci> ModifiedJulianDay 0     -- => 1858-11-17
ghci> ModifiedJulianDay 55807 -- => 2011-09-03

ghci> :t ModifiedJulianDay 55807 -- =>ModifiedJulianDay 55807 :: Day
ghci> show  ModifiedJulianDay {toModifiedJulianDay=55807} -- => "2011-09-03"

ghci > ModifiedJulianDay 55807 -- > 2011-09-03
ghci > let (ModifiedJulianDay n) = fromGregorian 2011 9 3
ghci > n -- >  55807

ghci> let day= fromGregorian 1858 11 17
ghci> :t day -- => day :: Day
ghci> fromEnum day                      -- => 0
ghci> fromEnum $ fromGregorian 2011 9 3 -- => 55807
  • Day型は Integer の newtype ですから無限リストも作れます。
ghci> take 10 [fromGregorian 2011 9 25 ..]
  -- =>
  [2011-09-25,2011-09-26,2011-09-27,2011-09-28,2011-09-29,2011-09-30,
   2011-10-01,2011-10-02,2011-10-03,2011-10-04]

ghci> take 10 [ModifiedJulianDay {toModifiedJulianDay=0}..]
  -- =>
  [1858-11-17,1858-11-18,1858-11-19,1858-11-20,1858-11-21,1858-11-22,
   1858-11-23,1858-11-24,1858-11-25,1858-11-26]

ghci >  take 10 [ModifiedJulianDay 0..]
 -- > [1858-11-17,1858-11-18,1858-11-19,1858-11-20,1858-11-21,1858-11-22,
       1858-11-23,1858-11-24,1858-11-25,1858-11-26]
ghci> let today = fromGregorian 2011 9 3
ghci> today            -- => 2011-09-03
ghci> succ today       -- => 2011-09-04
ghci> pred today       -- => 2011-09-02
ghci> fromEnum today   -- => 55807
ghci> fromEnum (fromGregorian 1858 11 17) -- => 0
ghci> (toEnum 0)::Day  -- => 1858-11-17
  • Day 型の加算・減算
> let today = fromGregorian 2011 9 3
-- 365日後
> addDays 365  today       -- => 2012-09-02
> addDays (-7) today       -- => 2011-08-27

> diffDays (fromGregorian 2012 4 5) (fromGregorian 2011 9 3)    -- => 215
> diffDays (fromGregorian 2011 9 3) ((toEnum 0)::Day)           -- => 55807
> diffDays (fromGregorian 2099 12 31) (fromGregorian 2000 1 1)  -- => 36524
  • toGregorianの返す型はタプルですが、fromGregorian の引数は部分適用できるようにカリー化されています。
ghci> toGregorian today        -- => (2011,9,3)
ghci> fromGregorian 2012 4 5   -- => 2012-04-05

ghci> fromGregorianValid 2012 4 5   -- => Just 2012-04-05
ghci> fromGregorianValid 2012 4 50  -- => Nothing
ghci> showGregorian today           -- => "2011-09-03"
  • 1月の日数を返す関数。
> map (gregorianMonthLength 2000) [1..12]  -- =>  [31,29,31,30,31,30,31,31,30,31,30,31]
> map (gregorianMonthLength 2011) [1..12]  -- =>  [31,28,31,30,31,30,31,31,30,31,30,31]
  • 月数を加算。
ghci> addGregorianMonthsClip 1 $ fromGregorian 2012 1 31
-- => 2012-02-29
ghci> addGregorianMonthsClip 1 $ fromGregorian 2010 1 31
-- => 2010-02-28
ghci> addGregorianMonthsClip 1 $ fromGregorian 2010 12 1
-- => 2011-01-01
ghci> addGregorianMonthsRollOver 1 $ fromGregorian 2010 12 1
-- => 2011-01-01
ghci> addGregorianMonthsRollOver 1 $ fromGregorian 2010 12 31
-- => 2011-01-31
ghci> addGregorianMonthsRollOver 1 $ fromGregorian 2010 1 31
-- => 2010-03-03
ghci> addGregorianMonthsRollOver 1 $ fromGregorian 2010 1 28
-- => 2010-02-28
ghci> addGregorianMonthsRollOver 1 $ fromGregorian 2010 1 29
-- => 2010-03-01
  • 年を加算。
ghci> addGregorianYearsClip 3 $ fromGregorian 2010 2 29
-- => 2013-02-28
ghci> addGregorianYearsClip 3 $ fromGregorian 2010 2 2
-- => 2013-02-02
ghci> addGregorianYearsClip 3 $ fromGregorian 2010 2 28
-- => 2013-02-28
ghci> addGregorianYearsRollOver 3 $ fromGregorian 2010 2 28
-- => 2013-02-28
ghci> addGregorianYearsRollOver 3 $ fromGregorian 2010 2 29
-- => 2013-02-28
ghci> addGregorianYearsRollOver 2 $ fromGregorian 2004 2 29
-- => 2006-03-01
ghci> addGregorianYearsRollOver 2 $ fromGregorian 2004 2 28
-- => 2006-02-28
> map (\x->(x,isLeapYear x)) [2000..2010] 
-- =>
  [(2000,True),(2001,False),(2002,False),(2003,False),(2004,True),
   (2005,False),(2006,False),(2007,False),(2008,True),(2009,False),
   (2010,False)]
  • 月末の日付を得る。(2012/10/30 追記)
import Data.Time.Calendar

endOfMonth :: Day -> Day
endOfMonth d = let (y,m,_) = toGregorian d 
               in  fromGregorian y m (gregorianMonthLength y m)

{-
> endOfMonth $ fromGregorian 2012 10 1  --> 2012-10-31
> endOfMonth $ fromGregorian 2012 2 10  --> 2012-02-29
> endOfMonth $ fromGregorian 2012 3 10  --> 2012-03-31
-}
  • 今日の日付を得る。(2013/05/05 追記)
import System.Time  hiding (Day)
import Data.Time.Calendar

getToday :: IO Day
getToday = do
  t <- getClockTime
  (CalendarTime yyyy mm dd _ _ _ _ _ _ _ _ _) <- toCalendarTime t
  return $ fromGregorian (fromIntegral yyyy) ((fromEnum mm)+1) dd

> getToday >>= \t -> return $ show t -- > "2013-05-05"