Catalog PyPI
PACKAGE EXPLORERView in registry

adif-io

PyPI

Input and output of ADIF radio amateur log files.

amateurhamradio
Versions collected1
Direct dependencies0From release 0.6.1
Last observed20 Oct, 19:31 UTC

README

Version 0.6.1 documentation

This is an ADIF parser in Python.

Actual usage

Main result of parsing: List of QSOs:

  • Each QSO is represented by a special-purpose Python mapping.
  • Keys in that mapping are ADIF field names in upper case,
  • value for a key is whatever was found in the ADIF, as a string (though some values are converted to upper case on output),
  • you can access individual field values via either qso[fieldname] or qso.get(fieldname) (depending on which behavior you want when your field does not exist),
  • and you can access all keys of that mapping via 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:

  • The ADIF string of the headers or that of a QSO are each terminated by a \n.
  • ADIF allows lower- and upper case field names. You can feed either to this software.
  • Field names are consistently converted to upper case internally.
  • Any non-field text in the header or in a QSO or between QSOs is ignored. (This may change at some undetermined time in the future.)
  • Value content is always a string.
  • Fields with zero-length content are treated as non-existent.
  • The output of a single QSO has a few important fields first, then all other fields in alphabetic order. The details may change over time.
  • Some QSO fields, in particular 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.

Convenience file writing

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.

Time on and time off

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])

Geographic coordinates - to some degree

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).

ADIF version

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.)

Output of ADIF

Not supported: ADIF data types.

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.

Only ADI.

This parser only handles ADI files. It knows nothing of the ADX file format.

Sample code

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):

Downloads & popularity

Awaiting observations

No metric observations yet

Download counts and popularity will appear here as the collector receives them.

Versions 1

Collected releases
VersionPublishedDirect dependenciesOpen version
0.6.11 Feb 20260

Package advisories

0
No matching advisories collected

This describes our collected data; it is not a security assessment.

Repository

ACCESSIBLE
andreas_krueger_py/adif_io

Shared repository data · GitLab

License detected in repository
Apache License 2.0
License branch / ref
main
Contributor identities
3 · complete API traversal
License file
Contributors · showing 3 of 3 collected
ContributorContributions
Andreas Krüger19
Andreas Krüger3
Simonas Kareiva1

Default-branch author identities; GitLab commit counts exclude merge commits.

Public sponsor counts have not been collected. These signals belong to the repository owner.

Downloads & popularity

3identities
Gauge · observed snapshots23 Oct 2026
31.50
2026-10-20: 3 · observed 2026-10-20T20:14:59.2899793Z2026-10-21: 3 · observed 2026-10-21T20:15:17.3209642Z2026-10-22: 3 · observed 2026-10-22T20:15:37.6935101Z2026-10-23: 3 · observed 2026-10-23T20:16:06.6787368Z
20 Oct23 Oct

gitlab · 4 observations shown. Missing days are unknown.

View observed values
Date / source periodValueObserved at
2026-10-2332026-10-23T20:16:06.6787368Z
2026-10-2232026-10-22T20:15:37.6935101Z
2026-10-2132026-10-21T20:15:17.3209642Z
2026-10-2032026-10-20T20:14:59.2899793Z

Metadata history

Meaningful changes
Current collected state Current

Observed since 20 Oct, 19:31 UTC

No superseded metadata states collected. Unchanged observations do not create new revisions.

Record identity

package:1fdca582220970b0556d22dd94f06beda640a65a26459b65aa209f73369550e4