sfeed_ass.c - randomcrap - random crap programs of varying quality
(HTM) git clone git://git.codemadness.org/randomcrap
(DIR) Log
(DIR) Files
(DIR) Refs
(DIR) README
(DIR) LICENSE
---
sfeed_ass.c (5099B)
---
1 /* Actually Simple Syndication specification: https://tilde.town/~dzwdz/ass/ */
2 #include <stdio.h>
3 #include <string.h>
4 #include <stdlib.h>
5
6 enum { FieldTime, FieldLink, FieldTitle, FieldLast }; /* ass fields */
7
8 /* ctype-like macros, but always compatible with ASCII / UTF-8 */
9 #define ISCNTRL(c) ((c) < ' ' || (c) == 0x7f)
10 #define ISDIGIT(c) (((unsigned)c) - '0' < 10)
11 #define ISSPACE(c) ((c) == ' ' || ((((unsigned)c) - '\t') < 5))
12
13 /* Convert time fields. Returns a UNIX timestamp. */
14 long long
15 datetounix(long long year, int mon, int day, int hour, int min, int sec)
16 {
17 static const long secs_through_month[] = {
18 0, 31 * 86400, 59 * 86400, 90 * 86400,
19 120 * 86400, 151 * 86400, 181 * 86400, 212 * 86400,
20 243 * 86400, 273 * 86400, 304 * 86400, 334 * 86400 };
21 int is_leap = 0, cycles, centuries = 0, leaps = 0, rem;
22 long long t;
23
24 if (year - 2ULL <= 136) {
25 leaps = (year - 68) >> 2;
26 if (!((year - 68) & 3)) {
27 leaps--;
28 is_leap = 1;
29 } else {
30 is_leap = 0;
31 }
32 t = 31536000 * (year - 70) + 86400 * leaps;
33 } else {
34 cycles = (year - 100) / 400;
35 rem = (year - 100) % 400;
36 if (rem < 0) {
37 cycles--;
38 rem += 400;
39 }
40 if (!rem) {
41 is_leap = 1;
42 } else {
43 if (rem >= 300)
44 centuries = 3, rem -= 300;
45 else if (rem >= 200)
46 centuries = 2, rem -= 200;
47 else if (rem >= 100)
48 centuries = 1, rem -= 100;
49 if (rem) {
50 leaps = rem / 4U;
51 rem %= 4U;
52 is_leap = !rem;
53 }
54 }
55 leaps += 97 * cycles + 24 * centuries - is_leap;
56 t = (year - 100) * 31536000LL + leaps * 86400LL + 946684800 + 86400;
57 }
58 t += secs_through_month[mon];
59 if (is_leap && mon >= 2)
60 t += 86400;
61 return t + (86400LL * (day - 1)) + (3600LL * hour) + (60LL * min) + sec;
62 }
63
64 /* Get timezone from string, return time offset in seconds from UTC */
65 long
66 gettzoffset(const char *s)
67 {
68 const char *p;
69 long tzhour = 0, tzmin = 0;
70 size_t i;
71
72 for (; ISSPACE((unsigned char)*s); s++)
73 ;
74 if (*s != '-' && *s != '+')
75 return 0;
76
77 for (i = 0, p = s + 1; i < 2 && ISDIGIT((unsigned char)*p); i++, p++)
78 tzhour = (tzhour * 10) + (*p - '0');
79 if (*p == ':')
80 p++;
81 for (i = 0; i < 2 && ISDIGIT((unsigned char)*p); i++, p++)
82 tzmin = (tzmin * 10) + (*p - '0');
83 return ((tzhour * 3600) + (tzmin * 60)) * (s[0] == '-' ? -1 : 1);
84 }
85
86 /* Parse time string `s` into the UNIX timestamp `tp`.
87 Returns 0 on success or -1 on failure. */
88 int
89 parsetime(const char *s, long long *tp)
90 {
91 int va[6] = { 0 }, i, v, vi = 0;
92
93 for (; ISSPACE((unsigned char)*s); s++)
94 ;
95
96 if (!ISDIGIT((unsigned char)s[0]) ||
97 !ISDIGIT((unsigned char)s[1]) ||
98 !ISDIGIT((unsigned char)s[2]) ||
99 !ISDIGIT((unsigned char)s[3])) {
100 /* formats "%Y-%m-%d %H:%M:%S", "%Y-%m-%dT%H:%M:%S" or "%Y%m%d%H%M%S" */
101 return -1;
102 }
103
104 /* parse time parts (and possibly remaining date parts) */
105 for (; *s && vi < 6; vi++) {
106 for (i = 0, v = 0; i < ((vi == 0) ? 4 : 2) &&
107 ISDIGIT((unsigned char)*s); s++, i++) {
108 v = (v * 10) + (*s - '0');
109 }
110 va[vi] = v;
111
112 if ((vi < 2 && *s == '-') ||
113 (vi == 2 && (*s == 'T' || ISSPACE((unsigned char)*s))) ||
114 (vi > 2 && *s == ':'))
115 s++;
116 }
117
118 /* skip milliseconds in for example: "%Y-%m-%dT%H:%M:%S.000Z" */
119 if (*s == '.') {
120 for (s++; ISDIGIT((unsigned char)*s); s++)
121 ;
122 }
123
124 /* invalid range */
125 if (va[0] < 0 || va[0] > 9999 ||
126 va[1] < 1 || va[1] > 12 ||
127 va[2] < 1 || va[2] > 31 ||
128 va[3] < 0 || va[3] > 23 ||
129 va[4] < 0 || va[4] > 59 ||
130 va[5] < 0 || va[5] > 60) /* allow leap second */
131 return -1;
132
133 *tp = datetounix(va[0] - 1900, va[1] - 1, va[2], va[3], va[4], va[5]) -
134 gettzoffset(s);
135
136 return 0;
137 }
138
139 /* Splits fields in the line buffer by replacing TAB separators with NUL ('\0')
140 * terminators and assign these fields as pointers. If there are less fields
141 * than expected then the field is an empty string constant. */
142 void
143 parseline(char *line, char *fields[FieldLast])
144 {
145 char *prev, *s;
146 size_t i;
147
148 for (prev = line, i = 0;
149 (s = strchr(prev, '\t')) && i < FieldLast - 1;
150 i++) {
151 *s = '\0';
152 fields[i] = prev;
153 prev = s + 1;
154 }
155 fields[i++] = prev;
156 for (; i < FieldLast; i++)
157 fields[i] = "";
158 }
159
160 /* print sfeed(5) field (non-content), it may not contain a TAB or newline */
161 void
162 printfield(const char *s)
163 {
164 for (; *s; s++)
165 if (!ISCNTRL((unsigned char)*s))
166 putchar(*s);
167 }
168
169 int
170 main(void)
171 {
172 char *fields[FieldLast], *line = NULL;
173 size_t linesiz = 0;
174 ssize_t linelen;
175 long long t;
176
177 while ((linelen = getline(&line, &linesiz, stdin)) > 0 &&
178 !ferror(stdout)) {
179 if (line[linelen - 1] == '\n')
180 line[--linelen] = '\0';
181 if (line[0] == '#' || line[0] == '\0' || linelen == 0)
182 continue; /* skip comments or empty lines */
183
184 /* parse a TAB-separated line into fields */
185 parseline(line, fields);
186
187 /* parse and print a valid time */
188 if (parsetime(fields[FieldTime], &t) != -1)
189 printf("%lld", t);
190 putchar('\t');
191 printfield(fields[FieldTitle]);
192 putchar('\t');
193 printfield(fields[FieldLink]);
194 putchar('\n');
195 }
196
197 /* error on reading input or writing output: exit code is non-zero */
198 return ferror(stdin) || fflush(stdout) || ferror(stdout);
199 }