dargslan-dns-resolver 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: dargslan-dns-resolver
3
+ Version: 1.0.0
4
+ Summary: DNS resolver tester — measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues
5
+ Author-email: Dargslan <info@dargslan.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://dargslan.com
8
+ Project-URL: Documentation, https://dargslan.com/blog
9
+ Project-URL: Repository, https://github.com/Dargslan
10
+ Project-URL: Free Cheat Sheets, https://dargslan.com/cheat-sheets
11
+ Project-URL: Linux & DevOps Books, https://dargslan.com/books
12
+ Keywords: dns,resolver,dnssec,nameserver,linux,sysadmin,devops,network
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: System :: Networking
19
+ Requires-Python: >=3.7
20
+ Description-Content-Type: text/markdown
21
+
22
+ # dargslan-dns-resolver
23
+
24
+ **DNS Resolver Tester** — Measure resolution time, compare resolvers, test DNSSEC, and detect DNS configuration issues. Zero external dependencies.
25
+
26
+ [![PyPI version](https://img.shields.io/pypi/v/dargslan-dns-resolver)](https://pypi.org/project/dargslan-dns-resolver/)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install dargslan-dns-resolver
33
+ ```
34
+
35
+ ## CLI Usage
36
+
37
+ ```bash
38
+ dargslan-dns report # Full DNS report
39
+ dargslan-dns resolve google.com # Quick resolution test
40
+ dargslan-dns dig google.com MX # Dig-style query
41
+ dargslan-dns compare 8.8.8.8 1.1.1.1 # Compare resolvers
42
+ dargslan-dns dnssec google.com # DNSSEC validation test
43
+ dargslan-dns reverse 8.8.8.8 # Reverse lookup
44
+ dargslan-dns config # Show resolver config
45
+ dargslan-dns json # JSON output
46
+ ```
47
+
48
+ ## Python API
49
+
50
+ ```python
51
+ from dargslan_dns_resolver import DNSResolver
52
+ dr = DNSResolver()
53
+ result = dr.resolve("dargslan.com")
54
+ comparison = dr.compare_resolvers(["8.8.8.8", "1.1.1.1", "9.9.9.9"])
55
+ dr.print_report()
56
+ ```
57
+
58
+ ## More from Dargslan
59
+
60
+ - [Dargslan.com](https://dargslan.com) — Linux & DevOps eBook Store
61
+ - [Free Cheat Sheets](https://dargslan.com/cheat-sheets)
62
+ - [Blog & Tutorials](https://dargslan.com/blog)
63
+
64
+ ## License
65
+
66
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,45 @@
1
+ # dargslan-dns-resolver
2
+
3
+ **DNS Resolver Tester** — Measure resolution time, compare resolvers, test DNSSEC, and detect DNS configuration issues. Zero external dependencies.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/dargslan-dns-resolver)](https://pypi.org/project/dargslan-dns-resolver/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## Installation
9
+
10
+ ```bash
11
+ pip install dargslan-dns-resolver
12
+ ```
13
+
14
+ ## CLI Usage
15
+
16
+ ```bash
17
+ dargslan-dns report # Full DNS report
18
+ dargslan-dns resolve google.com # Quick resolution test
19
+ dargslan-dns dig google.com MX # Dig-style query
20
+ dargslan-dns compare 8.8.8.8 1.1.1.1 # Compare resolvers
21
+ dargslan-dns dnssec google.com # DNSSEC validation test
22
+ dargslan-dns reverse 8.8.8.8 # Reverse lookup
23
+ dargslan-dns config # Show resolver config
24
+ dargslan-dns json # JSON output
25
+ ```
26
+
27
+ ## Python API
28
+
29
+ ```python
30
+ from dargslan_dns_resolver import DNSResolver
31
+ dr = DNSResolver()
32
+ result = dr.resolve("dargslan.com")
33
+ comparison = dr.compare_resolvers(["8.8.8.8", "1.1.1.1", "9.9.9.9"])
34
+ dr.print_report()
35
+ ```
36
+
37
+ ## More from Dargslan
38
+
39
+ - [Dargslan.com](https://dargslan.com) — Linux & DevOps eBook Store
40
+ - [Free Cheat Sheets](https://dargslan.com/cheat-sheets)
41
+ - [Blog & Tutorials](https://dargslan.com/blog)
42
+
43
+ ## License
44
+
45
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,269 @@
1
+ """
2
+ dargslan-dns-resolver — DNS Resolver Tester
3
+
4
+ Measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues.
5
+ Zero external dependencies — uses socket and subprocess.
6
+
7
+ Homepage: https://dargslan.com
8
+ Books: https://dargslan.com/books
9
+ Blog: https://dargslan.com/blog
10
+ """
11
+
12
+ __version__ = "1.0.0"
13
+ __author__ = "Dargslan"
14
+ __url__ = "https://dargslan.com"
15
+
16
+ import socket
17
+ import subprocess
18
+ import time
19
+ import json
20
+ import re
21
+ import os
22
+
23
+
24
+ WELL_KNOWN_RESOLVERS = {
25
+ '8.8.8.8': 'Google DNS',
26
+ '8.8.4.4': 'Google DNS (secondary)',
27
+ '1.1.1.1': 'Cloudflare DNS',
28
+ '1.0.0.1': 'Cloudflare DNS (secondary)',
29
+ '9.9.9.9': 'Quad9 DNS',
30
+ '208.67.222.222': 'OpenDNS',
31
+ '208.67.220.220': 'OpenDNS (secondary)',
32
+ }
33
+
34
+
35
+ class DNSResolver:
36
+ """Test DNS resolver performance and configuration."""
37
+
38
+ def __init__(self):
39
+ pass
40
+
41
+ def _run(self, args, timeout=10):
42
+ try:
43
+ result = subprocess.run(args, capture_output=True, text=True, timeout=timeout)
44
+ return result.stdout.strip(), result.returncode
45
+ except (FileNotFoundError, subprocess.TimeoutExpired):
46
+ return '', 1
47
+
48
+ def get_system_resolvers(self):
49
+ resolvers = []
50
+ try:
51
+ with open('/etc/resolv.conf', 'r') as f:
52
+ for line in f:
53
+ line = line.strip()
54
+ if line.startswith('nameserver'):
55
+ parts = line.split()
56
+ if len(parts) >= 2:
57
+ ip = parts[1]
58
+ name = WELL_KNOWN_RESOLVERS.get(ip, '')
59
+ resolvers.append({'ip': ip, 'name': name})
60
+ except (FileNotFoundError, PermissionError):
61
+ pass
62
+ return resolvers
63
+
64
+ def resolve(self, hostname, record_type='A'):
65
+ start = time.time()
66
+ try:
67
+ if record_type == 'A':
68
+ results = socket.getaddrinfo(hostname, None, socket.AF_INET)
69
+ ips = list(set(r[4][0] for r in results))
70
+ elif record_type == 'AAAA':
71
+ results = socket.getaddrinfo(hostname, None, socket.AF_INET6)
72
+ ips = list(set(r[4][0] for r in results))
73
+ else:
74
+ ips = []
75
+
76
+ elapsed = (time.time() - start) * 1000
77
+ return {
78
+ 'hostname': hostname,
79
+ 'type': record_type,
80
+ 'results': ips,
81
+ 'time_ms': round(elapsed, 2),
82
+ 'success': True,
83
+ }
84
+ except socket.gaierror as e:
85
+ elapsed = (time.time() - start) * 1000
86
+ return {
87
+ 'hostname': hostname,
88
+ 'type': record_type,
89
+ 'error': str(e),
90
+ 'time_ms': round(elapsed, 2),
91
+ 'success': False,
92
+ }
93
+
94
+ def dig(self, hostname, record_type='A', server=None):
95
+ args = ['dig', '+noall', '+answer', '+stats', hostname, record_type]
96
+ if server:
97
+ args.insert(1, f'@{server}')
98
+
99
+ start = time.time()
100
+ output, rc = self._run(args)
101
+ elapsed = (time.time() - start) * 1000
102
+
103
+ records = []
104
+ query_time = None
105
+ for line in output.split('\n'):
106
+ if line.startswith(';; Query time:'):
107
+ match = re.search(r'(\d+)\s+msec', line)
108
+ if match:
109
+ query_time = int(match.group(1))
110
+ elif line.strip() and not line.startswith(';'):
111
+ records.append(line.strip())
112
+
113
+ return {
114
+ 'hostname': hostname,
115
+ 'type': record_type,
116
+ 'server': server,
117
+ 'records': records,
118
+ 'query_time_ms': query_time,
119
+ 'total_time_ms': round(elapsed, 2),
120
+ 'success': rc == 0,
121
+ }
122
+
123
+ def benchmark_resolver(self, server, domains=None, rounds=3):
124
+ if domains is None:
125
+ domains = ['google.com', 'github.com', 'amazon.com', 'cloudflare.com', 'wikipedia.org']
126
+
127
+ times = []
128
+ for domain in domains:
129
+ for _ in range(rounds):
130
+ result = self.dig(domain, server=server)
131
+ if result.get('query_time_ms') is not None:
132
+ times.append(result['query_time_ms'])
133
+ elif result.get('total_time_ms'):
134
+ times.append(result['total_time_ms'])
135
+
136
+ if not times:
137
+ return {'server': server, 'error': 'No successful queries'}
138
+
139
+ times.sort()
140
+ return {
141
+ 'server': server,
142
+ 'name': WELL_KNOWN_RESOLVERS.get(server, ''),
143
+ 'queries': len(times),
144
+ 'avg_ms': round(sum(times) / len(times), 2),
145
+ 'min_ms': round(times[0], 2),
146
+ 'max_ms': round(times[-1], 2),
147
+ 'p50_ms': round(times[len(times) // 2], 2),
148
+ }
149
+
150
+ def compare_resolvers(self, servers=None):
151
+ if servers is None:
152
+ servers = ['8.8.8.8', '1.1.1.1', '9.9.9.9']
153
+ results = []
154
+ for server in servers:
155
+ results.append(self.benchmark_resolver(server, rounds=2))
156
+ results.sort(key=lambda x: x.get('avg_ms', 9999))
157
+ return results
158
+
159
+ def check_dnssec(self, hostname):
160
+ output, rc = self._run(['dig', '+dnssec', '+short', hostname, 'A'])
161
+ has_rrsig = 'RRSIG' in output or bool(re.search(r'\bRRSIG\b', output))
162
+
163
+ output2, _ = self._run(['dig', '+noall', '+comments', hostname, 'A'])
164
+ ad_flag = 'flags:' in output2 and ' ad' in output2
165
+
166
+ return {
167
+ 'hostname': hostname,
168
+ 'dnssec_signed': has_rrsig,
169
+ 'ad_flag': ad_flag,
170
+ 'validated': ad_flag,
171
+ }
172
+
173
+ def reverse_lookup(self, ip):
174
+ try:
175
+ hostname = socket.gethostbyaddr(ip)
176
+ return {'ip': ip, 'hostname': hostname[0], 'aliases': hostname[1]}
177
+ except (socket.herror, socket.gaierror) as e:
178
+ return {'ip': ip, 'error': str(e)}
179
+
180
+ def check_resolv_conf(self):
181
+ info = {'resolvers': self.get_system_resolvers()}
182
+ content = ''
183
+ try:
184
+ with open('/etc/resolv.conf', 'r') as f:
185
+ content = f.read()
186
+ except (FileNotFoundError, PermissionError):
187
+ pass
188
+
189
+ for line in content.split('\n'):
190
+ line = line.strip()
191
+ if line.startswith('search ') or line.startswith('domain '):
192
+ info['search_domains'] = line.split()[1:]
193
+ if line.startswith('options '):
194
+ info['options'] = line.split()[1:]
195
+
196
+ is_symlink = os.path.islink('/etc/resolv.conf')
197
+ info['is_symlink'] = is_symlink
198
+ if is_symlink:
199
+ info['symlink_target'] = os.readlink('/etc/resolv.conf')
200
+
201
+ return info
202
+
203
+ def audit(self):
204
+ issues = []
205
+ resolvers = self.get_system_resolvers()
206
+
207
+ if not resolvers:
208
+ issues.append({'severity': 'critical', 'message': 'No DNS resolvers configured'})
209
+ elif len(resolvers) < 2:
210
+ issues.append({'severity': 'warning', 'message': 'Only one DNS resolver configured — no redundancy'})
211
+
212
+ for r in resolvers:
213
+ result = self.resolve('google.com')
214
+ if not result['success']:
215
+ issues.append({'severity': 'critical', 'message': f"Resolver {r['ip']} failed to resolve google.com"})
216
+ elif result['time_ms'] > 500:
217
+ issues.append({'severity': 'warning', 'message': f"DNS resolution slow ({result['time_ms']}ms)"})
218
+
219
+ return issues
220
+
221
+ def print_report(self, domains=None):
222
+ resolvers = self.get_system_resolvers()
223
+ resolv_info = self.check_resolv_conf()
224
+ issues = self.audit()
225
+
226
+ print(f"\n{'='*60}")
227
+ print(f" DNS Resolver Report")
228
+ print(f"{'='*60}")
229
+
230
+ print(f"\n System Resolvers ({len(resolvers)}):")
231
+ for r in resolvers:
232
+ name = f" ({r['name']})" if r['name'] else ''
233
+ print(f" {r['ip']}{name}")
234
+
235
+ if resolv_info.get('search_domains'):
236
+ print(f" Search Domains: {' '.join(resolv_info['search_domains'])}")
237
+ if resolv_info.get('is_symlink'):
238
+ print(f" resolv.conf -> {resolv_info.get('symlink_target', '?')}")
239
+
240
+ test_domains = domains or ['google.com', 'github.com', 'dargslan.com']
241
+ print(f"\n Resolution Tests:")
242
+ for d in test_domains:
243
+ r = self.resolve(d)
244
+ if r['success']:
245
+ print(f" {d}: {', '.join(r['results'][:3])} ({r['time_ms']}ms)")
246
+ else:
247
+ print(f" {d}: FAILED — {r.get('error','')}")
248
+
249
+ print(f"\n Resolver Comparison:")
250
+ comparison = self.compare_resolvers(rounds=1)
251
+ for c in comparison:
252
+ if c.get('error'):
253
+ print(f" {c['server']:>15s}: FAILED")
254
+ else:
255
+ name = f" ({c['name']})" if c.get('name') else ''
256
+ print(f" {c['server']:>15s}{name}: avg={c['avg_ms']}ms min={c['min_ms']}ms max={c['max_ms']}ms")
257
+
258
+ if issues:
259
+ print(f"\n Issues ({len(issues)}):")
260
+ for i in issues:
261
+ print(f" [{i['severity'].upper():8s}] {i['message']}")
262
+
263
+ print(f"\n{'='*60}")
264
+ print(f" More tools: https://dargslan.com")
265
+ print(f" eBooks: https://dargslan.com/books")
266
+ print(f"{'='*60}\n")
267
+
268
+
269
+ __all__ = ["DNSResolver"]
@@ -0,0 +1,55 @@
1
+ """CLI for dargslan-dns-resolver — https://dargslan.com"""
2
+ import argparse
3
+
4
+ def main():
5
+ parser = argparse.ArgumentParser(
6
+ description="Dargslan DNS Resolver — DNS resolver tester",
7
+ epilog="More tools at https://dargslan.com | eBooks: https://dargslan.com/books",
8
+ )
9
+ parser.add_argument("command", nargs="?", default="report",
10
+ choices=["report", "resolve", "dig", "compare", "dnssec", "reverse", "config", "issues", "json"],
11
+ help="Command (default: report)")
12
+ parser.add_argument("targets", nargs="*", help="Hostnames or IPs")
13
+ parser.add_argument("-s", "--server", help="DNS server to use")
14
+ parser.add_argument("-t", "--type", default="A", help="Record type (A, AAAA, MX, etc.)")
15
+ args = parser.parse_args()
16
+
17
+ from dargslan_dns_resolver import DNSResolver
18
+ dr = DNSResolver()
19
+ import json
20
+
21
+ targets = [t for t in args.targets if t not in ('report','resolve','dig','compare','dnssec','reverse','config','issues','json')]
22
+
23
+ if args.command == 'report': dr.print_report(targets or None)
24
+ elif args.command == 'resolve':
25
+ for t in targets or ['google.com']:
26
+ r = dr.resolve(t, args.type)
27
+ if r['success']:
28
+ print(f" {t}: {', '.join(r['results'])} ({r['time_ms']}ms)")
29
+ else:
30
+ print(f" {t}: FAILED — {r.get('error','')}")
31
+ elif args.command == 'dig':
32
+ for t in targets or ['google.com']:
33
+ r = dr.dig(t, args.type, server=args.server)
34
+ for rec in r['records']: print(f" {rec}")
35
+ if r.get('query_time_ms'): print(f" Query time: {r['query_time_ms']}ms")
36
+ elif args.command == 'compare':
37
+ for c in dr.compare_resolvers(targets or None):
38
+ if c.get('error'): print(f" {c['server']}: FAILED")
39
+ else: print(f" {c['server']:>15s}: avg={c['avg_ms']}ms")
40
+ elif args.command == 'dnssec':
41
+ for t in targets or ['google.com']:
42
+ r = dr.check_dnssec(t)
43
+ print(f" {t}: signed={r['dnssec_signed']} validated={r['validated']}")
44
+ elif args.command == 'reverse':
45
+ for t in targets:
46
+ r = dr.reverse_lookup(t)
47
+ print(f" {t}: {r.get('hostname', r.get('error',''))}")
48
+ elif args.command == 'config':
49
+ c = dr.check_resolv_conf()
50
+ for r in c['resolvers']: print(f" Resolver: {r['ip']} {r.get('name','')}")
51
+ elif args.command == 'issues':
52
+ for i in dr.audit(): print(f" [{i['severity'].upper()}] {i['message']}")
53
+ elif args.command == 'json': print(json.dumps(dr.audit(), indent=2))
54
+
55
+ if __name__ == "__main__": main()
@@ -0,0 +1,66 @@
1
+ Metadata-Version: 2.4
2
+ Name: dargslan-dns-resolver
3
+ Version: 1.0.0
4
+ Summary: DNS resolver tester — measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues
5
+ Author-email: Dargslan <info@dargslan.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://dargslan.com
8
+ Project-URL: Documentation, https://dargslan.com/blog
9
+ Project-URL: Repository, https://github.com/Dargslan
10
+ Project-URL: Free Cheat Sheets, https://dargslan.com/cheat-sheets
11
+ Project-URL: Linux & DevOps Books, https://dargslan.com/books
12
+ Keywords: dns,resolver,dnssec,nameserver,linux,sysadmin,devops,network
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: POSIX :: Linux
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Topic :: System :: Networking
19
+ Requires-Python: >=3.7
20
+ Description-Content-Type: text/markdown
21
+
22
+ # dargslan-dns-resolver
23
+
24
+ **DNS Resolver Tester** — Measure resolution time, compare resolvers, test DNSSEC, and detect DNS configuration issues. Zero external dependencies.
25
+
26
+ [![PyPI version](https://img.shields.io/pypi/v/dargslan-dns-resolver)](https://pypi.org/project/dargslan-dns-resolver/)
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install dargslan-dns-resolver
33
+ ```
34
+
35
+ ## CLI Usage
36
+
37
+ ```bash
38
+ dargslan-dns report # Full DNS report
39
+ dargslan-dns resolve google.com # Quick resolution test
40
+ dargslan-dns dig google.com MX # Dig-style query
41
+ dargslan-dns compare 8.8.8.8 1.1.1.1 # Compare resolvers
42
+ dargslan-dns dnssec google.com # DNSSEC validation test
43
+ dargslan-dns reverse 8.8.8.8 # Reverse lookup
44
+ dargslan-dns config # Show resolver config
45
+ dargslan-dns json # JSON output
46
+ ```
47
+
48
+ ## Python API
49
+
50
+ ```python
51
+ from dargslan_dns_resolver import DNSResolver
52
+ dr = DNSResolver()
53
+ result = dr.resolve("dargslan.com")
54
+ comparison = dr.compare_resolvers(["8.8.8.8", "1.1.1.1", "9.9.9.9"])
55
+ dr.print_report()
56
+ ```
57
+
58
+ ## More from Dargslan
59
+
60
+ - [Dargslan.com](https://dargslan.com) — Linux & DevOps eBook Store
61
+ - [Free Cheat Sheets](https://dargslan.com/cheat-sheets)
62
+ - [Blog & Tutorials](https://dargslan.com/blog)
63
+
64
+ ## License
65
+
66
+ MIT — see [LICENSE](LICENSE)
@@ -0,0 +1,9 @@
1
+ README.md
2
+ pyproject.toml
3
+ dargslan_dns_resolver/__init__.py
4
+ dargslan_dns_resolver/cli.py
5
+ dargslan_dns_resolver.egg-info/PKG-INFO
6
+ dargslan_dns_resolver.egg-info/SOURCES.txt
7
+ dargslan_dns_resolver.egg-info/dependency_links.txt
8
+ dargslan_dns_resolver.egg-info/entry_points.txt
9
+ dargslan_dns_resolver.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ dargslan-dns = dargslan_dns_resolver.cli:main
@@ -0,0 +1 @@
1
+ dargslan_dns_resolver
@@ -0,0 +1,31 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "dargslan-dns-resolver"
7
+ version = "1.0.0"
8
+ description = "DNS resolver tester — measure resolution time, test DNSSEC, compare resolvers, and detect DNS issues"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.7"
12
+ authors = [{name = "Dargslan", email = "info@dargslan.com"}]
13
+ keywords = ["dns", "resolver", "dnssec", "nameserver", "linux", "sysadmin", "devops", "network"]
14
+ classifiers = [
15
+ "Development Status :: 5 - Production/Stable",
16
+ "Intended Audience :: System Administrators",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: POSIX :: Linux",
19
+ "Programming Language :: Python :: 3",
20
+ "Topic :: System :: Networking",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://dargslan.com"
25
+ Documentation = "https://dargslan.com/blog"
26
+ Repository = "https://github.com/Dargslan"
27
+ "Free Cheat Sheets" = "https://dargslan.com/cheat-sheets"
28
+ "Linux & DevOps Books" = "https://dargslan.com/books"
29
+
30
+ [project.scripts]
31
+ dargslan-dns = "dargslan_dns_resolver.cli:main"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+