Skip to content

Commit 1677f47

Browse files
committed
add support for QTCs
1 parent fe209c2 commit 1677f47

File tree

5 files changed

+237
-0
lines changed

5 files changed

+237
-0
lines changed

cabrillo.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func NewLog() *Log {
2525
Custom: make(map[Tag]string),
2626
QSOData: []QSO{},
2727
IgnoredQSOs: []QSO{},
28+
QTCData: []QTC{},
2829
}
2930
}
3031

@@ -50,6 +51,7 @@ type Log struct {
5051
Custom map[Tag]string
5152
QSOData []QSO
5253
IgnoredQSOs []QSO
54+
QTCData []QTC
5355
}
5456

5557
type Tag string
@@ -93,6 +95,7 @@ const (
9395
SpecificTag Tag = "SPECIFIC"
9496
SoapboxTag Tag = "SOAPBOX"
9597
QSOTag Tag = "QSO"
98+
QTCTag Tag = "QTC"
9699
XQSOTag Tag = "X-QSO"
97100
XPrefix = "X-"
98101
)
@@ -363,3 +366,21 @@ type QSOInfo struct {
363366
Call callsign.Callsign
364367
Exchange []string
365368
}
369+
370+
type QTC struct {
371+
Frequency QSOFrequency
372+
Mode QSOMode
373+
Timestamp time.Time
374+
RxCall callsign.Callsign
375+
QTCHeader QTCHeader
376+
TxCall callsign.Callsign
377+
QTCTime QTCTime
378+
QTCCall callsign.Callsign
379+
QTCNumber QTCNumber
380+
}
381+
382+
type QTCHeader string
383+
384+
type QTCTime string
385+
386+
type QTCNumber int

parse.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,14 @@ var tagParsers = map[Tag]tagParser{
318318
log.QSOData = append(log.QSOData, qso)
319319
return nil
320320
}),
321+
QTCTag: tagParserFunc(func(log *Log, value string) error {
322+
qtc, err := ParseQTC(value)
323+
if err != nil {
324+
return err
325+
}
326+
log.QTCData = append(log.QTCData, qtc)
327+
return nil
328+
}),
321329
XQSOTag: tagParserFunc(func(log *Log, value string) error {
322330
qso, err := ParseQSO(value)
323331
if err != nil {
@@ -393,3 +401,44 @@ func parseQSOInfo(columns []string) (QSOInfo, error) {
393401

394402
return result, nil
395403
}
404+
405+
func ParseQTC(s string) (QTC, error) {
406+
columns := qsoColumnSeparator.Split(s, -1)
407+
if len(columns) < 10 {
408+
return QTC{}, fmt.Errorf("not enough QTC columns: %d", len(columns))
409+
}
410+
411+
timestamp, err := ParseTimestamp(columns[2] + " " + columns[3])
412+
if err != nil {
413+
return QTC{}, err
414+
}
415+
rxCall, err := callsign.Parse(columns[4])
416+
if err != nil {
417+
return QTC{}, err
418+
}
419+
txCall, err := callsign.Parse(columns[6])
420+
if err != nil {
421+
return QTC{}, err
422+
}
423+
qtcCall, err := callsign.Parse(columns[8])
424+
if err != nil {
425+
return QTC{}, err
426+
}
427+
qtcNumber, err := strconv.Atoi(columns[9])
428+
if err != nil {
429+
return QTC{}, err
430+
}
431+
432+
var result QTC
433+
result.Frequency = QSOFrequency(columns[0])
434+
result.Mode = QSOMode(columns[1])
435+
result.Timestamp = timestamp
436+
result.RxCall = rxCall
437+
result.QTCHeader = QTCHeader(columns[5])
438+
result.TxCall = txCall
439+
result.QTCTime = QTCTime(columns[7])
440+
result.QTCCall = qtcCall
441+
result.QTCNumber = QTCNumber(qtcNumber)
442+
443+
return result, nil
444+
}

parse_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ func TestParser_ParseAllTags(t *testing.T) {
100100
"SOAPBOX: With an extra line and an empty line in between.",
101101
"QSO: 3559 CW 1999-03-06 0711 DL1ABC 599 B01 W1AW 599 001 0",
102102
"X-QSO: 3559 CW 1999-03-06 0712 DL1ABC 599 B01 N5KO 599 001 1",
103+
"QTC: 21011 CW 2024-08-10 1028 DL1ABC 7/10 K5XYZ 1332 S59XYZ 112",
103104
"X-CUSTOM: this is content in a custom tag",
104105
"END-OF-LOG:",
105106
}
@@ -174,6 +175,19 @@ func TestParser_ParseAllTags(t *testing.T) {
174175
Transmitter: 1,
175176
},
176177
}, actualLog.IgnoredQSOs, "ignored qsos")
178+
assert.Equal(t, []QTC{
179+
{
180+
Frequency: QSOFrequency("21011"),
181+
Mode: QSOModeCW,
182+
Timestamp: time.Date(2024, time.August, 10, 10, 28, 0, 0, time.UTC),
183+
RxCall: callsign.MustParse("DL1ABC"),
184+
QTCHeader: QTCHeader("7/10"),
185+
TxCall: callsign.MustParse("K5XYZ"),
186+
QTCTime: "1332",
187+
QTCCall: callsign.MustParse("S59XYZ"),
188+
QTCNumber: QTCNumber(112),
189+
},
190+
}, actualLog.QTCData, "qtc data")
177191
}
178192

179193
func TestParseOperators(t *testing.T) {

testdata/wae.v3.cabrillo

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
START-OF-LOG: 3.0
2+
CATEGORY: Single-OP low
3+
CONTEST: WAE CW
4+
LOCATION: DX
5+
CALLSIGN: DL1ABC
6+
CLUB: Bavarian Contest Club
7+
CLAIMED-SCORE: 4000
8+
OPERATORS: DL1ABC
9+
ADDRESS-CITY: Musterstadt
10+
ADDRESS-STATE-PROVINCE:
11+
ADDRESS-POSTALCODE: 12345
12+
ADDRESS-COUNTRY: FED. REP. OF GERMANY
13+
GRID-LOCATOR: JN59AA
14+
CREATED-BY: N1MM Logger+ 1.0.10854.0, modified by DXHF upload.
15+
QSO: 28005 CW 2025-08-09 1230 DL1NEO 599 0001 3V8LL 599 0196
16+
QSO: 21005 CW 2025-08-09 1231 DL1NEO 599 0002 H25A 599 0465
17+
QSO: 14001 CW 2025-08-09 1235 DL1NEO 599 0003 KC1XX 599 0621
18+
QSO: 14013 CW 2025-08-09 1236 DL1NEO 599 0004 AA3B 599 0455
19+
QSO: 14016 CW 2025-08-09 1236 DL1NEO 599 0005 N9NC 599 0134
20+
QSO: 14021 CW 2025-08-09 1237 DL1NEO 599 0006 WS9M 599 0162
21+
QSO: 14017 CW 2025-08-09 1240 DL1NEO 599 0007 CT3MD 599 0000
22+
QSO: 14019 CW 2025-08-09 1241 DL1NEO 599 0008 VE3NNT 599 0240
23+
QSO: 14019 CW 2025-08-09 1242 DL1NEO 599 0009 RU9AD 599 0234
24+
QSO: 14003 CW 2025-08-09 1242 DL1NEO 599 0010 JE6RPM 599 0087
25+
QSO: 21056 CW 2025-08-10 1754 DL1NEO 599 0011 K3AJ 599 0652
26+
QSO: 21056 CW 2025-08-10 1754 DL1NEO 599 0012 K3AJ 599 0000
27+
QTC: 21056 CW 2025-08-10 1755 DL1NEO 112/2 K3AJ 1752 DL8TG 0072
28+
QTC: 21056 CW 2025-08-10 1755 DL1NEO 112/2 K3AJ 1753 DL5KUD 0179
29+
QSO: 21034 CW 2025-08-10 1756 DL1NEO 599 0013 W1KM 599 0726
30+
QSO: 14032 CW 2025-08-10 1758 DL1NEO 599 0014 EA8RM 599 0728
31+
QTC: 14032 CW 2025-08-10 1758 DL1NEO 57/10 EA8RM 1623 DR3W 0417
32+
QTC: 14032 CW 2025-08-10 1759 DL1NEO 57/10 EA8RM 1623 SP4A 0545
33+
QTC: 14032 CW 2025-08-10 1759 DL1NEO 57/10 EA8RM 1624 DF6PB 0287
34+
QTC: 14032 CW 2025-08-10 1759 DL1NEO 57/10 EA8RM 1624 E70X 0086
35+
QTC: 14032 CW 2025-08-10 1759 DL1NEO 57/10 EA8RM 1625 HA5AQ 0222
36+
QTC: 14032 CW 2025-08-10 1759 DL1NEO 57/10 EA8RM 1625 UT2II 0307
37+
QTC: 14032 CW 2025-08-10 1759 DL1NEO 57/10 EA8RM 1625 YO9SP 0321
38+
QTC: 14032 CW 2025-08-10 1800 DL1NEO 57/10 EA8RM 1625 II2Q 0954
39+
QTC: 14032 CW 2025-08-10 1800 DL1NEO 57/10 EA8RM 1626 DL7AW 0097
40+
QTC: 14032 CW 2025-08-10 1800 DL1NEO 57/10 EA8RM 1626 DK1KC 0196
41+
QSO: 14048 CW 2025-08-10 1801 DL1NEO 599 0015 UN3M 599 1383
42+
QTC: 14048 CW 2025-08-10 1801 DL1NEO 166/9 UN3M 1732 ON7ET 0028
43+
QTC: 14048 CW 2025-08-10 1801 DL1NEO 166/9 UN3M 1732 LY5T 0064
44+
QTC: 14048 CW 2025-08-10 1801 DL1NEO 166/9 UN3M 1733 G4ERW 0014
45+
QTC: 14048 CW 2025-08-10 1801 DL1NEO 166/9 UN3M 1744 E70X 0104
46+
QTC: 14048 CW 2025-08-10 1802 DL1NEO 166/9 UN3M 1749 DL3UB 0016
47+
QTC: 14048 CW 2025-08-10 1802 DL1NEO 166/9 UN3M 1750 UX9Q 0022
48+
QTC: 14048 CW 2025-08-10 1802 DL1NEO 166/9 UN3M 1751 SP9XCN 0212
49+
QTC: 14048 CW 2025-08-10 1802 DL1NEO 166/9 UN3M 1752 R6KEE 0028
50+
QTC: 14048 CW 2025-08-10 1802 DL1NEO 166/9 UN3M 1753 YU5R 0307
51+
QSO: 14043 CW 2025-08-10 1803 DL1NEO 599 0016 E29TGW 599 0638
52+
QTC: 14043 CW 2025-08-10 1803 DL1NEO 56/10 E29TGW 1601 LY5W 0646
53+
QTC: 14043 CW 2025-08-10 1803 DL1NEO 56/10 E29TGW 1604 OK1LV 0054
54+
QTC: 14043 CW 2025-08-10 1804 DL1NEO 56/10 E29TGW 1611 DL5ANS 0125
55+
QTC: 14043 CW 2025-08-10 1804 DL1NEO 56/10 E29TGW 1611 OL1T 0064
56+
QTC: 14043 CW 2025-08-10 1804 DL1NEO 56/10 E29TGW 1612 YO8BDW 0031
57+
QTC: 14043 CW 2025-08-10 1804 DL1NEO 56/10 E29TGW 1613 II2Q 0948
58+
QTC: 14043 CW 2025-08-10 1804 DL1NEO 56/10 E29TGW 1613 R6AF 0170
59+
QTC: 14043 CW 2025-08-10 1804 DL1NEO 56/10 E29TGW 1613 MM1E 0088
60+
QTC: 14043 CW 2025-08-10 1804 DL1NEO 56/10 E29TGW 1614 UA6AK 0080
61+
QTC: 14043 CW 2025-08-10 1805 DL1NEO 56/10 E29TGW 1615 OH2PM 0113
62+
QSO: 21009 CW 2025-08-10 1806 DL1NEO 599 0017 PY2PLL 599 0491
63+
QTC: 21009 CW 2025-08-10 1806 DL1NEO 87/6 PY2PLL 1755 OR7K 0113
64+
QTC: 21009 CW 2025-08-10 1806 DL1NEO 87/6 PY2PLL 1800 DF1DT 0202
65+
QTC: 21009 CW 2025-08-10 1806 DL1NEO 87/6 PY2PLL 1801 DL8TG 0077
66+
QTC: 21009 CW 2025-08-10 1807 DL1NEO 87/6 PY2PLL 1802 EA4M 0025
67+
QTC: 21009 CW 2025-08-10 1807 DL1NEO 87/6 PY2PLL 1803 DH8WE 0131
68+
QTC: 21009 CW 2025-08-10 1807 DL1NEO 87/6 PY2PLL 1805 DL1ABR 0124
69+
QSO: 21021 CW 2025-08-10 1808 DL1NEO 599 0018 CB3A 599 0502
70+
QSO: 21022 CW 2025-08-10 1809 DL1NEO 599 0019 NN7CW 599 0786
71+
QSO: 21001 CW 2025-08-10 1809 DL1NEO 599 0020 KC1XX 599 1552
72+
QTC: 21001 CW 2025-08-10 1810 DL1NEO 133/10 KC1XX 1402 DK9ZQ 0107
73+
QTC: 21001 CW 2025-08-10 1810 DL1NEO 133/10 KC1XX 1402 SV8OVJ 0174
74+
QTC: 21001 CW 2025-08-10 1810 DL1NEO 133/10 KC1XX 1403 OM3CFR 0056
75+
QTC: 21001 CW 2025-08-10 1810 DL1NEO 133/10 KC1XX 1403 DK9IP 0350
76+
QTC: 21001 CW 2025-08-10 1810 DL1NEO 133/10 KC1XX 1404 SN3C 0013
77+
QTC: 21001 CW 2025-08-10 1810 DL1NEO 133/10 KC1XX 1404 HA7A 0437
78+
QTC: 21001 CW 2025-08-10 1811 DL1NEO 133/10 KC1XX 1406 DL3MXX 0132
79+
QTC: 21001 CW 2025-08-10 1811 DL1NEO 133/10 KC1XX 1406 OM2VL 0858
80+
QTC: 21001 CW 2025-08-10 1811 DL1NEO 133/10 KC1XX 1409 OE1CIW 0132
81+
QTC: 21001 CW 2025-08-10 1811 DL1NEO 133/10 KC1XX 1409 S51Z 0258
82+
QSO: 21022 CW 2025-08-10 1812 DL1NEO 599 0021 AA3B 599 1313
83+
QTC: 21022 CW 2025-08-10 1812 DL1NEO 132/10 AA3B 1729 DJ3RA 0062
84+
QTC: 21022 CW 2025-08-10 1812 DL1NEO 132/10 AA3B 1732 9A5X 0086
85+
QTC: 21022 CW 2025-08-10 1813 DL1NEO 132/10 AA3B 1733 DM6EE 0184
86+
QTC: 21022 CW 2025-08-10 1813 DL1NEO 132/10 AA3B 1734 9A3YT 0160
87+
QTC: 21022 CW 2025-08-10 1813 DL1NEO 132/10 AA3B 1736 DL2RUG 0077
88+
QTC: 21022 CW 2025-08-10 1813 DL1NEO 132/10 AA3B 1737 DJ2FR 0080
89+
QTC: 21022 CW 2025-08-10 1813 DL1NEO 132/10 AA3B 1738 MM1E 0129
90+
QTC: 21022 CW 2025-08-10 1813 DL1NEO 132/10 AA3B 1738 DK7HA 0292
91+
QTC: 21022 CW 2025-08-10 1814 DL1NEO 132/10 AA3B 1739 OY1CT 0044
92+
QTC: 21022 CW 2025-08-10 1814 DL1NEO 132/10 AA3B 1743 DL5MY 0029
93+
QSO: 21019 CW 2025-08-10 1815 DL1NEO 599 0022 WT2P 599 0483
94+
QSO: 21004 CW 2025-08-10 1815 DL1NEO 599 0023 PY3TD 599 0510
95+
QTC: 21004 CW 2025-08-10 1816 DL1NEO 51/10 PY3TD 0638 DK3AX 0039
96+
QTC: 21004 CW 2025-08-10 1816 DL1NEO 51/10 PY3TD 1648 DD5M 0332
97+
QTC: 21004 CW 2025-08-10 1816 DL1NEO 51/10 PY3TD 1650 EC1A 0079
98+
QTC: 21004 CW 2025-08-10 1817 DL1NEO 51/10 PY3TD 1645 DL5AZZ 0026
99+
QTC: 21004 CW 2025-08-10 1817 DL1NEO 51/10 PY3TD 1651 ER6NEN 0096
100+
QTC: 21004 CW 2025-08-10 1817 DL1NEO 51/10 PY3TD 1654 PA0M 0047
101+
QTC: 21004 CW 2025-08-10 1817 DL1NEO 51/10 PY3TD 1656 SP0O 0009
102+
QTC: 21004 CW 2025-08-10 1817 DL1NEO 51/10 PY3TD 1657 YO7ARZ 0042
103+
QTC: 21004 CW 2025-08-10 1818 DL1NEO 51/10 PY3TD 1700 EA5FID 0347
104+
QTC: 21004 CW 2025-08-10 1819 DL1NEO 51/10 PY3TD 1709 OL1T 0116
105+
QSO: 21013 CW 2025-08-10 1820 DL1NEO 599 0024 FY5KE 599 1614
106+
QTC: 21013 CW 2025-08-10 1820 DL1NEO 161/10 FY5KE 1731 IU0EDV 0115
107+
QTC: 21013 CW 2025-08-10 1821 DL1NEO 161/10 FY5KE 1732 DK8NC 0003
108+
QTC: 21013 CW 2025-08-10 1821 DL1NEO 161/10 FY5KE 1733 DL7URH 0072
109+
QTC: 21013 CW 2025-08-10 1821 DL1NEO 161/10 FY5KE 1736 DL3MXX 0158
110+
QTC: 21013 CW 2025-08-10 1821 DL1NEO 161/10 FY5KE 1738 DH8WE 0121
111+
QTC: 21013 CW 2025-08-10 1821 DL1NEO 161/10 FY5KE 1739 DJ3GE 0030
112+
QTC: 21013 CW 2025-08-10 1822 DL1NEO 161/10 FY5KE 1740 Z33B 0020
113+
QTC: 21013 CW 2025-08-10 1822 DL1NEO 161/10 FY5KE 1740 DF0SL 0003
114+
QTC: 21013 CW 2025-08-10 1822 DL1NEO 161/10 FY5KE 1740 IK7NXU 0040
115+
QTC: 21013 CW 2025-08-10 1822 DL1NEO 161/10 FY5KE 1741 LY5T 0068
116+
END-OF-LOG:

write.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ func WriteWithTags(w io.Writer, l *Log, appendTX bool, ommitIfEmpty bool, tags .
6060
return err
6161
}
6262

63+
err = writeQTCs(w, QTCTag, l.QTCData)
64+
if err != nil {
65+
return err
66+
}
67+
6368
err = writeRows(w, row{EndOfLogTag, "", false})
6469
return err
6570
}
@@ -279,3 +284,35 @@ func writeQSO(w io.Writer, tag Tag, data QSO, appendTX bool) error {
279284
_, err = fmt.Fprintln(w)
280285
return err
281286
}
287+
288+
func writeQTCs(w io.Writer, tag Tag, data []QTC) error {
289+
for _, qtc := range data {
290+
err := writeQTC(w, tag, qtc)
291+
if err != nil {
292+
return err
293+
}
294+
}
295+
return nil
296+
}
297+
298+
func writeQTC(w io.Writer, tag Tag, data QTC) error {
299+
// # QRG(7) MODE(4) DATE(12) TIME(6) CALL-RX(10) QTC-GRP(5) CALL-TX(9) TIME-QSO(6) CALL-QSO(8) NR-QSO(3)
300+
// QTC: 21011 CW 2024-08-10 1028 DL1XYZ 7/10 K5XYZ 1332 S59XYZ 112
301+
_, err := fmt.Fprintf(w, "%s: %s %s %s %s %s %s %s %s %03d",
302+
tag,
303+
data.Frequency,
304+
data.Mode,
305+
formatTimestamp(data.Timestamp),
306+
data.RxCall.String(),
307+
data.QTCHeader,
308+
data.TxCall.String(),
309+
data.QTCTime,
310+
data.QTCCall,
311+
data.QTCNumber,
312+
)
313+
if err != nil {
314+
return err
315+
}
316+
_, err = fmt.Fprintln(w)
317+
return err
318+
}

0 commit comments

Comments
 (0)