Observed since 20 Oct, 19:31 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
Input and output of ADIF radio amateur log files.
Main result of parsing: List of QSOs:
qso[fieldname] or qso.get(fieldname)
(depending on which behavior you want when your field does not exist),qso.keys().Order of QSOs in the list is same as in ADIF file.
Secondary result of parsing: The ADIF headers. This is returned as a Python mapping as well.
Normally, you'd call adif_io.read_from_file(filename). This reads
UTF-8. As UTF-8 is a proper subset of the US-ASCII presently
demanded by the ADIF spec, all conforming ADI files are read.
You need not know this as long as dealing with spec-conforming ADI files only, but this software expects field counts to count Unicode code points, not bytes.
If you have some file that is not an ADI file, but would be an ADI file if it didn't have non-US-ASCII characters in some other encoding, you can specify that encoding (if Python knows about it). Here is an example:
qsos, header = adif_io.read_from_file(filename, encoding="ISO-8859-15")
Finally, you can also provide a string with an ADI-file's content, as follows:
import adif_io
qsos, header = adif_io.read_from_string(
"A sample ADIF content for demonstration.\n"
"<adif_ver:5>3.1.0<eoh>\n"
"<QSO_DATE:8>20190714 <time_on:4>1140<CALL:5>LY0HQ"
"<mode:2>CW<BAND:3>40M<RST_SENT:3>599<RST_RCVD:3>599"
"<STX_STRING:2>28<SRX_STRING:4>LRMD<EOR>\n"
"<QSO_DATE:8>20190714<TIME_ON:4>1130<CALL:5>SE9HQ<MODE:2>CW<FREQ:1>7"
"<BAND:3>40M<RST_SENT:3>599<RST_RCVD:3>599"
"<SRX_STRING:3>SSA<DXCC:3>284<EOR>")
After this setup, print(header) will print out a valid ADIF file start:
<ADIF_VER:5>3.1.0 <EOH>
(This starts with a blank space, as the ADIF spec demands a header must not
start with the < character.)
And
for qso in qsos:
print(qso)
prints
<QSO_DATE:8>20190714 <TIME_ON:4>1140 <CALL:5>LY0HQ <MODE:2>CW <BAND:3>40M <RST_RCVD:3>599 <RST_SENT:3>599 <SRX_STRING:4>LRMD <STX_STRING:2>28 <EOR>
<QSO_DATE:8>20190714 <TIME_ON:4>1130 <CALL:5>SE9HQ <FREQ:1>7 <MODE:2>CW <BAND:3>40M <DXCC:3>284 <RST_RCVD:3>599 <RST_SENT:3>599 <SRX_STRING:3>SSA <EOR>
So str(qso) for a single QSO generates that QSO as an ADIF string.
Fine points:
\n.CALL and MODE, are automatically converted to upper case on output.
This is not done systematically (for other fields that would also benefit from this),
and the details may change.You can write QSOs to a new ADI file by calling:
adif_io.write_to_file(filename, qsos)
If you have headers, too, do:
adif_io.write_to_file(filename, qsos, headers)
These two normally writes UTF-8, but that can be tweaked with an
optional encoding= argument, similar to what read_from_file
expects.
Given one qso dict, you can also have the QSO's start time calculated as a Python datetime.datetime value:
adif_io.time_on(qsos[0])
If your QSO data also includes TIME_OFF fields (and, ideally, though
not required, QSO_DATE_OFF), this will also work:
adif_io.time_off(qsos[0])
ADIF uses a somewhat peculiar 11 character XDDD MM.MMM format to
code geographic coordinates (fields LAT or LON). The more common
format these days are simple floats that code degrees. You can convert
from one to the other:
adif_io.degrees_from_location("N052 26.592") # Result: 52.4432
adif_io.location_from_degrees(52.4432, True) # Result: "N052 26.592"
The additional bool argument of location_from_degrees should be
True for latitudes (N / S) and False for longitudes (E / W).
There is little ADIF-version-specific here. (Everything should work with ADI-files of ADIF version 3.1.6, if you want to nail it.)
This parser knows nothing about ADIF data types or enumerations. Everything is a string. So in that sense, this parser is fairly simple.
But it does correcly handle things like:
<notes:66>In this QSO, we discussed ADIF and in particular the <eor> marker.
So, in that sense, this parser is somewhat sophisticated.
This parser only handles ADI files. It knows nothing of the ADX file format.
Here is some sample code:
import adif_io
qsos_raw, adif_header = adif_io.read_from_file("log.adi")
# The QSOs are probably sorted by QSO time already, but make sure:
qsos_raw_sorted = sorted(qsos_raw, key = adif_io.time_on)
Pandas / Jupyter users may want to add import pandas as pd
up above and continue like this:
qsos = pd.DataFrame(qsos_raw_sorted)
qsos.info()
I'm aware of (listing those does not neccessarily imply an endorsement):
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.6.1 | 1 Feb 2026 | 0 |
This describes our collected data; it is not a security assessment.
Shared repository data · GitLab
| Contributor | Contributions |
|---|---|
| Andreas Krüger | 19 |
| Andreas Krüger | 3 |
| Simonas Kareiva | 1 |
Default-branch author identities; GitLab commit counts exclude merge commits.
Public sponsor counts have not been collected. These signals belong to the repository owner.
gitlab · 4 observations shown. Missing days are unknown.
| Date / source period | Value | Observed at |
|---|---|---|
| 2026-10-23 | 3 | 2026-10-23T20:16:06.6787368Z |
| 2026-10-22 | 3 | 2026-10-22T20:15:37.6935101Z |
| 2026-10-21 | 3 | 2026-10-21T20:15:17.3209642Z |
| 2026-10-20 | 3 | 2026-10-20T20:14:59.2899793Z |
Observed since 20 Oct, 19:31 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:1fdca582220970b0556d22dd94f06beda640a65a26459b65aa209f73369550e4