screen shot of DCF77 signal and interpretatation

German radio time signal from Mainflingen near Frankfurt am Main

The signal has an official range of 2000 km which covers most of Western Europe.
More details in Wikipedia

Some years ago I wrote a Turbobasic program to interpret and display the signal. It uses a receiver from Conrad electronics. Its signal goes through an ATmega8 in the hope of sharpening it up a bit - probably not essential - and is fed into a printer port (D sub 25) of a computer running MS-DOS 6.22 - obsolete but rock-solid. The signal format itself has not changed so you may like to convert the program ( listed below 'as is' ) to a modern platform. It can be used for the indoor sport of catching leap seconds.

I later wrote a pendulum-clock-checking program which compares the DCF77 signal to the swings of the pendulum and displays the variations on a graph.

' This gets the signal from the DCF77 transmitter and prints the time to screen
' It takes about 2 minutes for the program get synchronised with the signal
' There is no signal on the 59th second so there is a pause 
' in the display on the 58th second while waiting for the end of the 59th second.

' The program uses a Conrad receiver module via an ATmega8 chip and LPT2:

' The receiver should be sited away from a CRT monitor.
' The ferrite core of the receiver should be oriented flat-on to Frankfurt-am-Main.

' The signal consists of a 77.5 kHz signal which is reduced in amplitude to 15%
' to form spaces at 1 second intervals.
' The second starts on the leading (falling) flank of the space.
' The spaces are either 100 ms long which means: 0
'               OR      200 ms long which means: 1
' The series of 1's and 0's provides the time code.
' The 59th space in each minute is omitted to syncronise the receiver with
' the code.

' The program makes a note of the marks rather than the spaces since they are said
' to be less affected by thunderstorms.

' The code includes 3 parity bits (even parity) and 3 fixed bits. Actually, the
' second "fixed" bit, normally low, indicates a transmitter malfunction if high.
' The program "beeps" if the parity or fixed bits are incorrect.

' This version of the program uses a software loop to get the time values.
' Previous versions used the Turbo Basic TIMER function but its accuracy
' falls off after 54 ms.

' The marks are all circa 50 ms too short.

' Another approach would be to measure the events using the external
' microcontroller which would then relay the results via the serial port.

Start:

ON KEY(1) GOSUB Sayonara
KEY(1) ON
ON ERROR GOTO Start

CLS
?
? "Input -------  DCF time signal 77.5 kHz:    Space:      ms   Mark:      ms
COLOR  2,0
?
? "-------------------------------------------------------------------------------"
? "  10's ------             1         2         3         4         5         6"
? "Seconds -----    123456789012345678901234567890123456789012345678901234567890
? "                               this line is overwritten while synchronising"
COLOR 14,0
? "Signal ------
COLOR 2,0
? "Values ------                        1248124 124812 1248121241248112481248
? "  10's ------                            000     00     00       0    0000
COLOR 10,0
? "Interpretation  Weather code--- CET- Mins--- Hours- Days- Wk Mon  Year----
COLOR 14,0
? "Parity ------                  0    1       P      P                      P
COLOR 2,0
? "-------------------------------------------------------------------------------"
COLOR 7,0
?
? "DCF time ----
?
? "DCF date ----
?
? "PC time -----

'################# MAIN LOOP #####################
DO
'------------------------------------- Get signal - METHOD 1
   Count = 0                         ' Reset
   WHILE Signal = OldSignal          ' Wait while the signal is unchanged
     Signal = INP(&H278+1)           ' read parallel port LPT2:
     INCR Count                      ' count number of times Signal was read
   WEND
   OldSignal = Signal                ' update Signal
   Msec = INT(Count/277)             ' fudge Count into "milliseconds" Msec
                                     ' (divisor to suit your computer speed)
' ? Msec;
'------------------------------------- Get signal - METHOD 2
' MTIMER                             ' Start micro timer
' WHILE Signal = OldSignal           ' Wait while the signal is unchanged
'   Signal = INP(&H278+1)            ' read parallel port LPT2:
' WEND
' OldSignal = Signal                 ' update Signal
' Msec = INT(MTIMER/1000)            ' read and cook microseconds
'  ? Msec

   ' The lengths of the incoming marks and spaces are not recorded very
   ' accurately - there is interference on the leading flanks of the marks -
   ' so there is a range:

   '  50 -  150 Msecs counts as a short space (nominally 100 Msecs)
   ' 151 -  250 Msecs             long space  (nominally 200 Msecs)
   ' 500 -  850 Msecs             short mark, (nominally 800 Msecs)
   ' 851 - 1200 Msecs            long  mark, (nominally 900 Msecs)
   '     > 1200 Msecs              end of minute marker

   ' This program reads the marks only (it could be the spaces - or both)

  SELECT CASE Msec        ' Sort out what length of mark (or space)
                                            ' was received and act on it

      CASE 500 TO 850        ' 800 ms. mark following a 200 ms. space
          CALL GetSignal(Sec,Min,Hour,Day,WDay,Month,Year,CET$,Imm$,Pm,Ph,Pc)
                                            ' Interpret that signal
          CALL ShowSec(Sec,"1",Msec)    ' Display seconds and 1
      CASE 851 TO 1200      ' 900 ms. mark following a 100 ms. space
          CALL ShowSec(Sec,"0",Msec)    ' Display seconds and 0
      CASE > 1200                ' long "end of minute" marker
          CALL ShowSec(Sec,"0-0",Msec)                 ' Display seconds
          CALL ShowTime(Hour,Min,CET$,Imm$,Pm,Ph)    ' Display time and CET/CEST
          CALL ShowCalendar(Day,WDay,Month,Year,Pc)  ' Display calendar
          SOUND 1000, .5
          Sec = 0
     CASE 10 TO 499
          CALL ShowSpace(Msec)
  END SELECT
LOOP

'#################### SUB ROUTINES ##############

SUB ShowSpace(Msec)
  LOCATE 2,42 : ? "   "             ' blink signal
  LOCATE 2,51 : COLOR 15,0 : ? USING "#####"; Msec              ' print space
END SUB

'---------------------------
SUB ShowSec(Sec,Signal$,Msec)                                   ' display stuff
  IF Sec = 0 THEN LOCATE 7,75  : ? "   "                        ' wipe pointer
  INCR Sec
  LOCATE 2,  42 :     COLOR 14,0   : ? Signal$
  LOCATE 2,  67 :     COLOR 15,0   : ? USING "#####"; Msec      ' mark
  LOCATE 7,  Sec+16                              : ? " |"       ' pointer
  LOCATE 8,  Sec+16:  COLOR 14,0   : ? Signal$  : COLOR 15,0
  LOCATE 15, 22:      COLOR 15,0   : ? RIGHT$(STR$(100 + Sec),2)
  LOCATE 19, 16:      COLOR  7,0   : ? TIME$
  LOCATE  8, 17:      COLOR 15,0   : ? " "                      ' wipe signal
  IF Sec = 59 THEN
     LOCATE 7,Sec + 17 :             ? " |"                     ' minute marker
  END IF
END SUB

'----------------------------
SUB ShowTime(Hour,Min,CET$,Imm$,Ph,Pm)         ' display hours, mins, CETs
  LOCATE 15, 16:      COLOR 15,0  : ? USING "##:##:"; Hour, Min;

IF Imm$ <> "" THEN
  LOCATE 20, 26:  COLOR 15,0  : ? Imm$ : EXIT SUB
ELSE
  LOCATE 20, 26: ? SPACE$(30)
END IF

  LOCATE 15, 26:      COLOR 15,0  : ? CET$
  IF Ph/2 <> INT(Ph/2) THEN BEEP                         ' beep if parity error
  IF Pm/2 <> INT(Pm/2) THEN BEEP                         ' ditto
END SUB
'----------------------------
SUB ShowCalendar(Day,WDay,Month,Year,Pc)                 ' display calendar

SELECT CASE WDay
  CASE 1:  WDay$  = "Monday,"
  CASE 2:  WDay$  = "Tuesday,"
  CASE 3:  WDay$  = "Wednesday,"
  CASE 4:  WDay$  = "Thursday,"
  CASE 5:  WDay$  = "Friday,"
  CASE 6:  WDay$  = "Saturday,"
  CASE 7:  WDay$  = "Sunday,"
END SELECT

SELECT CASE Month
  CASE 1:  Month$ = "January"
  CASE 2:  Month$ = "February"
  CASE 3:  Month$ = "March"
  CASE 4:  Month$ = "April"
  CASE 5:  Month$ = "May"
  CASE 6:  Month$ = "June"
  CASE 7:  Month$ = "July"
  CASE 8:  Month$ = "August"
  CASE 9:  Month$ = "September"
  CASE 10: Month$ = "October"
  CASE 11: Month$ = "November"
  CASE 12: Month$ = "December"
END SELECT

LOCATE 17, 16  : COLOR 15,0
  ? WDay$; Day; Month$;
  ? USING " 20##        "; Year
  COLOR 7,0
  IF Pc/2 <> INT(Pc/2) THEN BEEP        ' beep if parity error
END SUB
'-----------------------------
'Interpret the signal bits according to the second on which they arrived

SUB GetSignal(Sec,Min,Hour,Day,WDay,Month,Year,CET$,Imm$,Pm,Ph,Pc)
  Imm$ = ""
  SELECT CASE Sec
    CASE 0  '   : BEEP                   ' this bit is always reset !
    CASE 1 TO 14:
               Min = 0 : Hour = 0 : Day = 0 : WDay = 0 : Month = 0 : Year = 0
               Pm = 0 : Ph = 0 : Pc = 0
      '1 to 14 is a coded commercial weather forecast. The code has been broken
      'by Microcontroller.net but not published. It took them 3 years.
      'I believe the time provided a key to the code
    CASE 15 : CET$  = "Abnormal transmitter operation"
    CASE 16 : Imm$  = "A time change is imminent     "
    CASE 17 : CET$  = "Central European Summer Time  "
    CASE 18 : CET$  = "Central European Time         "
    CASE 19 : Imm$  = "A leap second is imminent     "
      '  20                                 this bit is always set
    CASE 21 : Min   = Min + 1    : INCR Pm
    CASE 22 : Min   = Min + 2    : INCR Pm
    CASE 23 : Min   = Min + 4    : INCR Pm
    CASE 24 : Min   = Min + 8    : INCR Pm
    CASE 25 : Min   = Min + 10   : INCR Pm
    CASE 26 : Min   = Min + 20   : INCR Pm
    CASE 27 : Min   = Min + 40   : INCR Pm
    CASE 28 :                    : INCR Pm  'parity bit minutes
    CASE 29 : Hour  = Hour + 1   : INCR Ph
    CASE 30 : Hour  = Hour + 2   : INCR Ph
    CASE 31 : Hour  = Hour + 4   : INCR Ph
    CASE 32 : Hour  = Hour + 8   : INCR Ph
    CASE 33 : Hour  = Hour + 10  : INCR Ph
    CASE 34 : Hour  = Hour + 20  : INCR Ph
    CASE 35 :                    : INCR Ph  'parity bit hours
    CASE 36 : Day   = Day + 1    : INCR Pc
    CASE 37 : Day   = Day + 2    : INCR Pc
    CASE 38 : Day   = Day + 4    : INCR Pc
    CASE 39 : Day   = Day + 8    : INCR Pc
    CASE 40 : Day   = Day + 10   : INCR Pc
    CASE 41 : Day   = Day + 20   : INCR Pc
    CASE 42 : WDay  = WDay + 1   : INCR Pc
    CASE 43 : WDay  = WDay + 2   : INCR Pc
    CASE 44 : WDay  = WDay + 4   : INCR Pc
    CASE 45 : Month = Month + 1  : INCR Pc
    CASE 46 : Month = Month + 2  : INCR Pc
    CASE 47 : Month = Month + 4  : INCR Pc
    CASE 48 : Month = Month + 8  : INCR Pc
    CASE 49 : Month = Month + 10 : INCR Pc
    CASE 50 : Year  = Year  + 1  : INCR Pc
    CASE 51 : Year  = Year  + 2  : INCR Pc
    CASE 52 : Year  = Year  + 4  : INCR Pc
    CASE 53 : Year  = Year  + 8  : INCR Pc
    CASE 54 : Year  = Year  + 10 : INCR Pc
    CASE 55 : Year  = Year  + 20 : INCR Pc
    CASE 56 : Year  = Year  + 40 : INCR Pc
    CASE 57 : Year  = Year  + 80 : INCR Pc
    CASE 58 :                    : INCR Pc          'parity bit calendar

 END SELECT
END SUB

Sayonara:
END