Swiss Railway Clock in Haskell

I wanted a small Haskell programming task to solve. I had just read about the controversy around the new Clock application in iOS 6 which is trademarked by the Swiss Federal Railway (see Swiss Railway Clock (Wikipedia)). The clock design is simple and elegant and I though that this would be a nice task to re-implement in Haskell.

I have used gloss for some projects before so this was my first choice to output some graphics. The project was small enough (58 LOC) to be completed in a few evenings.

This is the resulting code.

module Main (main) where
import Graphics.Gloss
import System.Time

type Len = Float
type Angle = Float
type SubSecond = Float
data Face = Face { r_face :: Len, thick :: Len }        -- Center is assumed at origo
data RLine = RLine { len :: Len, width :: Len, r_tip :: Len, col :: Color }
newtype Mark = MarkC { mRLine :: RLine }
newtype Hand = HandC { hRLine :: RLine }
data SecHand = SecHandC { hand :: RLine, r_cent :: Len, r_outer :: Len }

-- Draw RLine at a certain angle. These are drawn through the center of the faceto r_tip.
-- If len > r_tip then the line passes through origo. Angle 0 is represented at 12 a clock direction.
draw_line :: RLine -> Angle -> Picture
draw_line (RLine len width r_tip col) a = Color col $ Rotate a $ Translate 0 (-len/2+r_tip) $
                                          rectangleSolid width len where

-- Draw clock face with a surrounding circle at radius, r and hour and minute marks
draw_face :: Face -> Mark -> Mark -> Picture
draw_face (Face r w) (MarkC h) (MarkC m) = pictures $ face ++ hour_marks ++ min_marks where
                  hour_marks = map (\a -> draw_line h a) [0,30..330]
                  min_marks = map (\a -> draw_line m a) [0,6..354]    -- TODO: Rem. hour marks
                  face = [ThickCircle r w]

-- Draw clock hands according to calendar time, t
draw_hands :: ClockTime -> SubSecond -> Hand -> Hand -> SecHand -> Picture
draw_hands ct ss (HandC h) (HandC m) (SecHandC s r1 r2) = Color (col s) $
                                                       pictures $ hour ++ min ++ sec ++ dots where
                  t = toUTCTime ct
                  seconds = ss + (fromIntegral $ ctSec t)     -- set ss=0 if you want distinct seconds
                  minutes = fromIntegral $ ctMin t          
                  hours = fromIntegral $ ctHour t          
                  hour = [draw_line h (30 * (hours + minutes/60))]
                  min  = [draw_line m (6  * minutes)]
                  sec  = [draw_line s (6  * seconds)]
                  dots = [circleSolid r1, Translate sec_x sec_y (circleSolid r2)]
                  sec_x = (r_tip s) * cos(sec_radians)
                  sec_y = (r_tip s) * sin(sec_radians)
                  sec_radians = (pi/2) - (pi/30) * seconds

-- Get current local time zone diff in hours                   
get_timezone :: ClockTime -> IO Int
get_timezone ct = do cal <- toCalendarTime ct
                     return ((ctTZ cal) `div` 3600)

-- Draw clock with clock time (ct) and timezone (tz) (hours) and a time diff (td) (seconds) since start
drawClock :: ClockTime -> Int -> Float -> Picture
drawClock ct tz td = Translate trans trans $ Scale scale scale $ picture
               where
                 size = 600
                 scale = 0.4
                 trans = -0.0*(fromIntegral size)
                 picture = pictures [draw_face face m_hour m_min, draw_hands ct' subsec h_hour h_min h_sec]
                 -- Ugly way to handle timezone. This works because draw_hands use toUTCTime
                 ct' = addToClockTime (TimeDiff	0 0 0 tz 0 sec 0) ct
                 (sec, subsec) = properFraction td
                 face = Face 714 5
                 m_hour = MarkC $ RLine 158 54 690 black
                 m_min = MarkC $ RLine 68 12 690 black
                 h_hour = HandC $ RLine 590 54 386 black
                 h_min = HandC $ RLine 826 54 622 black
                 h_sec = SecHandC (RLine 626 16 445 red) 23 55

main = do
 ct <- getClockTime
 tz <- get_timezone ct
 animate 
   (InWindow "Swiss Railway Clock by Apple" (600,600) (100,100))
   white                   -- background color
   (drawClock ct tz)       -- picture to display

A screenshot of the resulting clock shown below. Unfortunately gloss does not yet support anti-aliasing which would make the graphics more smooth.

AppleClock.png

I have generated a binary application for MacOS Lion.

To generate a new binary i used the following commands.

ghc -O2 -dylib-install-name /Applications/Xcode.app/Contents/Developer//Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.7.sdk/usr/lib AppleClock.hs
strip AppleClock -o AppleClockStrip

./AppleClockStrip