Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 131 lines (103 sloc) 4.76 KB
#!/usr/bin/env python
import argparse
import re
import urllib
import json
import time
import datetime
# Parse the arguments.
parser = argparse.ArgumentParser(description='Given a birthdate, this program estimates a date of conception and returns the Billboard Hot 100 singles list for that date.')
parser.add_argument('date', help='a date in ISO 8601 format, "YYYY-MM-DD"')
parser.add_argument('apikey', help='your Billboard developer API key')
parser.add_argument('-n', '--number', type=int, help='number of songs to return')
parser.add_argument('-f', '--format', choices=['json', 'text'], default='text', help='output format')
group = parser.add_mutually_exclusive_group()
# The Billboard Hot 100 (singles)
group.add_argument('--singles', action='store_const', const=379, default=379, dest='chart')
# The Billboard 200 (albums)
group.add_argument('--albums', action='store_const', const=305, dest='chart')
args = parser.parse_args()
# Validate against ISO 8601.
if re.match(r'^\d{4}-\d{2}-\d{2}$', is None:
parser.error('Invalid date. Must be formatted "YYYY-MM-DD"')
# Set the year, month, and day.
year, month, day = map(int,'-'))
# Set the birth date.
birthDate =, month, day)
except ValueError as e:
parser.error('Invalid date. {error}'.format(error=e))
# Validate against earliest and latest date available. Feburary 13, 1959 is the
# earliest date available.
if birthDate <, 2, 13) or birthDate >
parser.error('Invalid date. Must be between 1959-02-13 and today.')
# Set the conception date. Human pregnancy is on average 40 weeks (280 days).
conceptionDate = birthDate - datetime.timedelta(days=280)
# Set start and end dates, 3 days before and after conception date. The
# Billboard Hot 100 came out every week, so a 7 day search is sufficient.
startDate = conceptionDate - datetime.timedelta(days=3)
endDate = conceptionDate + datetime.timedelta(days=3)
# Set paramater defaults.
start = 1
count = 50
conceptionSongs = []
# Iterate the requests.
while True:
# Set the parameters.
params = urllib.urlencode({'format': 'json',
'id': args.chart,
'api_key': args.apikey,
'sdate': startDate,
'edate': endDate,
'start': start,
'count': count})
# Set the URL opener.
opener = urllib.URLopener()
# API does not return JSON unless Accept-Encoding header is set to gzip.
opener.addheader('Accept-Encoding', 'gzip')
# Make the Billboard API request.
response ='{params}'.format(params=params))
except IOError as e:
# Decode JSON.
results = json.loads(
# Iterate the results.
for chartItem in results['searchResults']['chartItem']:
# Some results have no distrubution.
except KeyError:
chartItem['distribution'] = None
# Build a list containing songs.
# Set the new start paramater.
start = start + count
# Must sleep to comply with the 2 calls per second rate limit.
# Break out of the request loop when there are no more results.
if (results['searchResults']['firstPosition'] + count) > results['searchResults']['totalRecords']:
# Sort the song list according to rank.
conceptionSongs = sorted(conceptionSongs)
# Slice the list according to the -n argument.
if args.number is not None:
conceptionSongs = conceptionSongs[0:args.number]
# JSON output.
if args.format == 'json':
print json.dumps({'birthDate': birthDate.isoformat(),
'conceptionDate': conceptionDate.isoformat(),
'conceptionSongs': conceptionSongs})
# Default text output.
print 'Conception date: {conceptionDate}'.format(conceptionDate=conceptionDate.isoformat())
print 'Conception songs:'
for conceptionSong in conceptionSongs:
print '{rank}. "{song}" by {artist} ({distribution})'.format(rank=conceptionSong[0],