Skip to content

Instantly share code, notes, and snippets.

@hrishikeshrt
Last active October 31, 2024 07:48
Show Gist options
  • Save hrishikeshrt/0090a0460608728f32381164ea54865c to your computer and use it in GitHub Desktop.
Save hrishikeshrt/0090a0460608728f32381164ea54865c to your computer and use it in GitHub Desktop.
Hindu Calendar Utility using drikPanchang.com

Python class and a CLI for various calendar utilities according to Hindu Calendar. It uses drikPanchang.com for querying data.

Some Features:

  • Finding the Tithi of a particular date
  • Querying daily Panchang
  • Finding an occurrence of a particular Tithi in the current (or specified) year
  • Ability to specify date in both regional or Gregorian format
  • Display in regional language
  • ICS Calendar Creator (Plotting Janma-Tithi on the Gregorian calendar for the specified year)

Files:

  • hindu_calendar.py

    Core HinduCalendar class.

  • hcal_cli.py

    Provides a command-line interface for various querying options using HinduCalendar class.

  • ics_creator.py

    Accepts a .csv file containing events and creates a .ics file plotting the Tithi of birthdays/anniversaries on the Gregorian calendar file thus created is importable in Google Calendar.

Requirements:

requests, beautifulsoup4, geocoder, ics

CLI Installation:

Download the files into a directory of your choice. Place all the .py files beside eachother. Navigate to the directory.

$ pip install requests beautifulsoup4 geocoder ics
$ chmod +x hcal_cli.py ics_creator.py

Link it to any location in the path.

$ ln -s hcal_cli.py ~/bin/hcal
$ hcal -h
usage: hcal [-h] [-d date] [-f] [-r] [-m M] [-c] [-y year] [-l] [-s]

Hindu Calendar Utilities

optional arguments:
  -h, --help  show this help message and exit
  -d date     Date in dd/mm/yyyy
  -f          Fresh configuration
  -r          Input regional date
  -m M        Regional method to use
  -c          Find re-occurrence of Tithi of the 'date' in the current year
  -y year     Use with '-c' to specify year
  -l          Display in regional language
  -s          Show config and exit

Alternatively, one may run the file using python3 hcal_cli.py.

Class Usage:

Use help(HinduCalendar) in the python console to see detailed help.

>>> import datetime
>>> from hindu_calendar import HinduCalendar
>>> cal =HinduCalendar(method='marathi')
>>> cal.find_city('Mumbai')
>>> cal.set_city('1275339', 'Mumbai')
>>> today = datetime.datetime.today().strftime("%d/%m/%Y")
>>> cal.find_date(today)
>>> cal.find_occurrence('15/08/1947', year='2020')

CLI Usage:

$ hcal

The first run will interactively setup initial config. Subsequent runs will show the date object for 'today'

$ hcal -d dd/mm/yyyy

Returns the date object for the given date Other acceptable formats: (dd-mm-yyyy, dd:mm:yyyy, dd.mm.yyyy, dd, dd-mm)

$ hcal -d dd-mm-yyyy -m hindi

Specify which regional method to use to query and show the date object.

Available methods: marathi, hindi, bengali, tamil, telugu, kannada, gujarati, malayalam

$ hcal -d dd-mm-yyyy -r

-r flag indicates that the specified date is in regional format. Although the most natural way of specifying the regional dates is in terms of moon phase, the CLI expects the date to be specified in dd-mm-yyyy format.

dd - Date index. For a 'Ashtami' from the second half of the month, the date would thus not be 8, but 23 (15+8). mm - Month index according to the regional index. (e.g. Chaitra has index 01 in the North Indian (hindi) calendar, while the same month would have index 12 in the Bangali (bengali) calendar. yyyy - Year in the regional calendar.

$ hcal -d dd-mm-yyyy -c Finds the Tithi on the date dd-mm-yyyy, and then returns the date object corresponding to the occurrence of that Tithi in the current year. (Year can be specified with the -y flag, to go with the -c flag).

ICS Creation:

Following is a sample events.csv file.

date,regional_date,label,name,method,use_regional,include
15-08-1947,,Independence Day,India,marathi,0,1

The flag is_regional is 1 if the date column contains the date in the regional format. This can be useful for plotting festivals. The flag include is used to determine if that row will be processed or not. Value 1 indicates the row will be processed and an event will be created. Value 0 indicates the row will be ignored.

$ python3 ics_creator.py -i events.csv -o events.ics

This would go through each entry of events.csv, find out what was the Tithi on the date and then locate the Tithi in the current year (or the year specified with -y flag).

Ultimately, all these events will be written to a .ics file which can be imported in various calendars such as Google Calendar.

About Date Object:

Date object is a JSON object (dictionary) containing following entities,

  • method: method used to query this object
  • language: language being used to query this object
  • city: city used to query this object
  • ce_date: date in theg regorian calendar (common era)
  • ce_datestring: date string
  • event: special event according to Hindu calendar (if any)
  • regional_date: Hindu date (Tithi) (dd/mm/yyyy)
  • regional_datestring: string description of the Tithi
  • panchang: panchang entries
  • url: URL used to fetch this data
{
"method": "marathi",
"city": "Mumbai",
"ce_date": "15/08/1947",
"ce_datestring": "Friday, 15 August, 1947",
"event": "",
"regional_date": "29/05/1869",
"regional_datestring": "29, Shravana (Adhik), Krishna Paksha, Chaturdashi, 1869 Sarvajit, Shaka Samvat",
"panchang": {
"Sunrise": "06:19 AM",
"Sunset": "07:07 PM",
"Moonrise": "05:54 AM , Aug 16",
"Moonset": "06:29 PM",
"Shaka Samvat": "1869 Sarvajit",
"Lunar Month": "Shravana (Adhik)",
"Weekday": "Shukrawara",
"Paksha": "Krishna Paksha",
"Tithi": "Chaturdashi upto 08:21 PM",
"Nakshatra": "Pushya upto 08:09 PM",
"Yoga": "Vyatipata upto 09:57 PM",
"Karana": "Vishti upto 10:11 AM",
"Karana #1": "Shakuni upto 08:21 PM",
"Sunsign": "Karka",
"Moonsign": "Karka",
"Rahu Kalam": "11:07 AM to 12:43 PM",
"Gulikai Kalam": "07:55 AM to 09:31 AM",
"Yamaganda": "03:55 PM to 05:31 PM",
"Abhijit": "12:17 PM to 01:08 PM",
"Dur Muhurtam": "08:53 AM to 09:44 AM",
"Dur Muhurtam #1": "01:08 PM to 02:00 PM",
"Amrit Kalam": "02:30 PM to 03:55 PM"
},
"url": "https://www.drikpanchang.com/marathi/panchang/marathi-month-panchang.html?date=15/08/1947&geoname-id=1275339"
}
date regional_date label name method use_regional include
15-08-1947 Independence Day India marathi 0 0
BEGIN:VCALENDAR
CALSCALE:GREGORIAN
BEGIN:VEVENT
X-GOOGLE-CALENDAR-CONTENT-DISPLAY:chip
X-GOOGLE-CALENDAR-CONTENT-ICON:https://calendar.google.com/googlecalendar/images/cake.gif
DTSTART;VALUE=DATE:20200818
SUMMARY:India's Independence Day
UID:[email protected]
END:VEVENT
PRODID:HinduCalendar() using drikPanchang.com by Hrishikesh Terdalkar
VERSION:2.0
END:VCALENDAR
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Thu Feb 6 21:12:00 2020
Updated on Thu Aug 27 14:19:52 2020
Updated on Sat Sep 12 15:33:58 2020
@author: Hrishikesh Terdalkar
CLI using HinduCalendar Class
Requirements:
- geocoder (only for auto-detection of the city)
"""
import os
import json
import logging
import argparse
from datetime import datetime as dt
import geocoder
from hindu_calendar import HinduCalendar
###############################################################################
# GeoNames username for auto-detecting city
GEONAMES_USERNAME = 'hindu_calendar'
###############################################################################
def configure(fresh=False, **kwargs):
"""
Get or set configuration
config will be stored in ~/.hcal/config
Function call will first try to read from this file, if it exists.
Parameters
----------
fresh : bool, optional
If True, existing configuration, if any, will be ignored.
The default is False.
Returns
-------
config : dict
Configuration dictionary. Various (config_name, config_value)
"""
# ----------------------------------------------------------------------- #
home_dir = os.path.expanduser('~')
hcal_dir = os.path.join(home_dir, '.hcal')
storage_dir = os.path.join(hcal_dir, 'data')
config_file = os.path.join(hcal_dir, 'config')
log_file = os.path.join(hcal_dir, 'log')
if not os.path.isdir(hcal_dir):
os.makedirs(hcal_dir)
logging.basicConfig(filename=log_file,
format='[%(asctime)s] %(levelname)s: %(message)s',
datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO)
# ----------------------------------------------------------------------- #
if os.path.isfile(config_file) and not fresh:
with open(config_file, 'r') as f:
config = json.loads(f.read())
else:
logging.info("Initiating fresh config ...")
# default config
config = {
'geonames_username': GEONAMES_USERNAME,
'city': 'auto',
'method': 'marathi',
'regional_language': True,
'geonames_id': '',
'storage_dir': storage_dir
}
# ------------------------------------------------------------------- #
# City
default_value = config['city']
detected_city = False
while True:
# --------------------------------------------------------------- #
if detected_city:
answer = detected_city
else:
prompt = f"City (default: {default_value}): "
answer = input(prompt).strip()
# --------------------------------------------------------------- #
if answer:
options = HinduCalendar.find_city(answer, n=10)
n_opt = len(options)
if n_opt == 1:
config['city'] = options[0]['city']
config['geonames_id'] = options[0]['id']
print(f"{options[0]['city']}, "
f"{options[0]['state']}, "
f"{options[0]['country']}")
break
else:
for idx, option in enumerate(options):
print(f"\t[{idx}]\t{option['city']}, "
f"{option['state']}, {option['country']}")
while True:
_prompt = f"Please choose your city [0-{n_opt-1}]: "
choice = input(_prompt).strip()
try:
choice = int(choice)
except Exception:
continue
if choice == -1:
ask_again = True
detected_city = False
break
else:
if 0 <= choice < n_opt:
config['city'] = options[choice]['city']
config['geonames_id'] = options[choice]['id']
ask_again = False
break
if not ask_again:
break
else:
ip = geocoder.ip('me', key=config['geonames_username'])
detected_city = ip.city
# --------------------------------------------------------------- #
# ------------------------------------------------------------------- #
# Method
default_value = config['method']
while True:
valid_methods = list(HinduCalendar.methods.keys())
prompt = f"Method (default: {default_value}): "
answer = input(prompt).strip().lower()
if answer:
if answer not in valid_methods:
logging.warning(f"Invalid method: {answer}")
print("Warning: no such method.\n"
f"Available: {valid_methods}")
else:
config['method'] = answer
break
else:
break
# ------------------------------------------------------------------- #
# ------------------------------------------------------------------- #
# Regional Language
default_value = 'yes' if config['regional_language'] else 'no'
while True:
valid_values = ['yes', 'no']
prompt = f"Use regional language? (default: {default_value}): "
answer = input(prompt).strip().lower()
if answer:
if answer not in valid_values:
logging.warning(f"Invalid value: {answer}")
print(f"Please answer {'/'.join(valid_values)}.")
else:
config['regional_language'] = answer == 'yes'
break
else:
break
# ------------------------------------------------------------------- #
# ------------------------------------------------------------------- #
# Storage Directory
prompt = f"Storage Path (default: {storage_dir}): "
answer = input(prompt).strip()
if not answer:
answer = storage_dir
if not os.path.isdir(answer):
os.makedirs(answer)
# ------------------------------------------------------------------- #
with open(config_file, 'w') as f:
f.write(json.dumps(config))
logging.info("Written config.")
if kwargs:
for arg, val in kwargs.items():
config[arg] = val
with open(config_file, 'w') as f:
f.write(json.dumps(config))
logging.info("Written config.")
return config
###############################################################################
def main():
desc = 'Hindu Calendar Utilities'
p = argparse.ArgumentParser(description=desc)
p.add_argument("-d", metavar='date', help="Date in dd/mm/yyyy")
p.add_argument("-f", help="Fresh configuration", action='store_true')
p.add_argument("-r", help="Input regional date", action='store_true')
p.add_argument("-m", help="Regional method to use")
p.add_argument("-c", action='store_true', help=("Find re-occurrence of "
"Tithi of the 'date' in "
"the current year"))
p.add_argument("-y", metavar='year', help="Use with '-c' to specify year")
p.add_argument("-l", action='store_true', help=("Display in regional "
"language"))
p.add_argument("-s", help="Show config and exit", action='store_true')
args = vars(p.parse_args())
# arguments
fresh = args['f']
date = args['d']
method = args['m']
regional = args['r']
show = args['s']
reoccurrence = args['c']
reyear = args['y']
# config
config = configure(fresh=fresh)
regional_language = (True
if args['l'] else
config['regional_language'])
if show:
print(json.dumps(config, indent=2, ensure_ascii=False))
return locals()
if method is None:
method = config['method']
if date is None:
date = dt.now().strftime("%d/%m/%Y")
calendar = HinduCalendar(method, city=config['city'],
regional_language=regional_language,
geonames_id=config['geonames_id'],
storage_dir=config['storage_dir'])
if reoccurrence:
if not reyear:
reyear = dt.now().year
date = calendar.find_occurrence(date, year=reyear, regional=regional)
else:
date = calendar.get_date(date, regional=regional)
print(json.dumps(date, indent=2, ensure_ascii=False))
return locals()
###############################################################################
if __name__ == '__main__':
locals().update(main())
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Tue Feb 4 15:03:43 2020
Updated on Thu Aug 27 14:19:52 2020
Updated on Sat Sep 12 15:33:58 2020
Updated on Thu Nov 5 19:11:23 2020
@author: Hrishikesh Terdalkar
Hindu Calendar Utility using drikPanchang.com
GeoNames Account: hindu_calendar (http://www.geonames.org/)
Thirdp-party Dependancies:
- requests
- dateparser
- beautifulsoup4
"""
###############################################################################
import os
import re
import json
import logging
from datetime import datetime as dt
from urllib.parse import urlparse
import requests
from bs4 import BeautifulSoup
import dateparser
HEADERS = {
'Authority': 'www.drikpanchang.com',
'Accept': ('text/html,application/xhtml+xml,application/xml;'
'q=0.9,image/webp,image/apng,*/*;'
'q=0.8,application/signed-exchange;v=b3;q=0.9'),
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'en-IN,en-GB;q=0.9,en-US;q=0.8,en;q=0.7',
'Upgrade-Insecure-Requests': '1',
'User-Agent': ('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36')
}
###############################################################################
class HinduCalendar():
'''
Hindu Calendar Utility (using drikPanchang.com)
'''
server_url = 'https://www.drikpanchang.com'
sitemap = {
'settings': '/settings/drikpanchang-settings.html'
}
methods = {
'assamese': {
'month': '/assamese/assamese-month-panjika.html',
'day': '/assamese/assamese-day-panjika.html',
'lang': 'as',
'language': 'Assamese (অসমীয়া)'
},
'bengali': {
'month': '/bengali/bengali-month-panjika.html',
'day': '/bengali/bengali-day-panjika.html',
'lang': 'bn',
'language': 'Bengali (বাংলা)',
},
'gujarati': {
'month': '/gujarati/panchang/gujarati-month-panchang.html',
'day': '/gujarati/panchang/gujarati-day-panchang.html',
'lang': 'gu',
'language': 'Gujarati (ગુજરાતી)'
},
'hindi': {
'month': '/panchang/month-panchang.html',
'day': '/panchang/day-panchang.html',
'lang': 'hi',
'language': 'Hindi (हिन्दी)'
},
# 'isckon': {
# 'month': '/iskcon/iskcon-month-calendar.html',
# 'day': '/iskcon/iskcon-day-calendar.html',
# 'lang': 'en',
# 'language': 'English'
# },
'kannada': {
'month': '/kannada/panchangam/kannada-month-panchangam.html',
'day': '/kannada/panchangam/kannada-day-panchangam.html',
'lang': 'kn',
'language': 'Kannada (ಕನ್ನಡ)',
},
'malayalam': {
'month': '/malayalam/malayalam-month-calendar.html',
'day': '/malayalam/malayalam-day-calendar.html',
# 'lang': 'ml',
# 'language': 'Malayalam (മലയാളം)',
'lang': 'en',
'language': 'English'
},
'marathi': {
'month': '/marathi/panchang/marathi-month-panchang.html',
'day': '/marathi/panchang/marathi-day-panchang.html',
'lang': 'mr',
'language': 'Marathi (मराठी)',
},
'nepali': {
'month': '/nepali/calendar/nepali-patro.html',
'day': '/nepali/calendar/nepali-day-patro.html',
# 'lang': 'ne',
# 'language': 'Nepali (नेपाली)',
'lang': 'en',
'language': 'English'
},
'oriya': {
'month': '/oriya/oriya-panji.html',
'day': '/oriya/oriya-day-panji.html',
'lang': 'or',
'language': 'Odia (ଓଡ଼ିଆ)',
},
'tamil': {
'month': '/tamil/tamil-month-panchangam.html',
'day': '/tamil/tamil-day-panchangam.html',
'lang': 'ta',
'language': 'Tamil (தமிழ்)',
},
'telugu': {
'month': '/telugu/panchanga/telugu-month-panchanga.html',
'day': '/telugu/panchanga/telugu-day-panchanga.html',
# 'lang': 'te',
# 'language': 'Telugu (తెలుగు)'
'lang': 'en',
'language': 'English'
}
}
def __init__(self, method='marathi', city='auto',
regional_language=False, geonames_id=None,
storage_dir=None):
self._session = requests.Session()
self.method = None
self.regional_language = regional_language
self.city = city
self.geonames_id = geonames_id
self.regional_lists = {}
self.storage_dir = storage_dir
self.set_method(method)
self.set_regional_language(regional_language)
self._session.headers.update(HEADERS)
def get_languages(self):
'dpLanguageSettingId'
def get_date_url(self, date, regional=False, day=False):
logging.info(
f"get_date_url(date={date}, regional={regional}, day={day})"
)
dmy_split = re.split(r'/|:|\.|-|,', re.sub(r'\s', '', date))
dd = dmy_split[0].zfill(2)
mm = dmy_split[1].zfill(2) if len(dmy_split) > 1 else dt.today().month
yyyy = dmy_split[2] if len(dmy_split) > 2 else dt.today().year
date = f'{dd}/{mm}/{yyyy}'
_parse = urlparse(self.method_day_url if day else self.method_url)
_query = f'lunar-date={date}' if regional else f'date={date}'
if self.geonames_id:
_query += f'&geoname-id={self.geonames_id}'
date_url = _parse._replace(query=_query).geturl()
return date_url
def get_details(self, date, regional=False):
"Work in Progress"
"TODO: Figure out if this is needed at all"
logging.info(
f"get_details(date={date}, regional={regional})"
)
date_url = self.get_date_url(date, regional=regional, day=True)
r = self.get(date_url)
content = r.content.decode('utf-8')
soup = BeautifulSoup(content, 'html.parser')
cell_divs = soup.find_all('div', {'class': 'dpTableCell'})
key_class = 'dpTableKey'
val_class = 'dpTableValue'
details = {}
for cell in cell_divs:
if key_class in cell['class']:
cell_key = cell.get_text()
if val_class in cell['class']:
cell_value = cell.get_text()
details[cell_key] = cell_value
return soup, details
def get_date(self, date, regional=False):
"""
Find the regional information about the specified date
This is a core function of the class.
Returns 'date_object', which in turn is the return type of
various other functions.
As a byproduct, it also saves various regional lists (such as month
names, nakshatra names etc).
Parameters
----------
date : str
Date in dd/mm/yyyy format.
regional : bool, optional
True, if input date is a regional date.
False, if the input date is CE date.
The default is False.
Returns
-------
date_object : dict
Date object contains following information:
The regional method that was used to find the date
Date and datestring in both regional as well as CE format
Panchang
"""
logging.info(
f"get_date(date={date}, regional={regional})"
)
date_url = self.get_date_url(date, regional=regional)
r = self.get(date_url)
content = r.content.decode('utf-8')
soup = BeautifulSoup(content, 'html.parser')
# panchang
day_div = soup.find('div', {'class': 'dpDayPanchangWrapper'})
panchang_div = day_div.find('div', {'class': 'dpPanchang'})
panchang = {}
for element in panchang_div.find_all('p', {'class': 'dpElement'}):
_key = element.find('span', {'class': 'dpElementKey'})
_key_text = _key.get_text(separator=' ', strip=True)
_val = element.find('span', {'class': 'dpElementValue'})
_val_text = _val.get_text(separator=' ', strip=True)
if _key_text in panchang:
while _key_text in panchang:
_split = _key_text.split(' #')
try:
_split[1] = str(int(_split[1]) + 1)
except IndexError:
_split.append('1')
_key_text = ' #'.join(_split)
panchang[_key_text] = _val_text
# regional month_list
scripts = soup.find_all('script')
for script in scripts:
# TODO: Find a better way than str(script)
# BeautifulSoup4 >= 4.9 stopped extracting text from script tags
script_text = str(script)
if 'var dpRegionalMonthList' in script_text:
regional_str = script_text.split(';')[-2].split('=')[1].strip()
regional_months = json.loads(regional_str)
# lunar and solar dates
head_div = soup.find('div', {'class': 'dpPHeaderContent'})
regional_div = head_div.find('div', {'class': 'dpPHeaderLeftContent'})
regional_datestring = regional_div.get_text(separator=', ', strip=True)
regional_split = regional_datestring.split(', ')
regional_dd = regional_split[0]
regional_month = regional_split[1].split()[0]
regional_mm = str(regional_months.index(regional_month) + 1).zfill(2)
regional_yy = regional_split[4].split()[0]
regional_date = dateparser.parse(
f'{regional_dd}/{regional_mm}/{regional_yy}',
settings={'DATE_ORDER': 'DMY'}
).strftime("%d/%m/%Y")
ce_div = head_div.find('div', {'class': 'dpPHeaderRightContent'})
# ce_date = dt.strptime(ce_div.get_text(separator=' ', strip=True),
# "%d %B %Y %A")
ce_date = dateparser.parse(ce_div.get_text(separator=' ', strip=True))
ce_datestring = ce_date.strftime("%A, %d %B, %Y")
ce_date = ce_date.strftime("%d/%m/%Y")
logging.info(f"CE: {ce_date}, Regional: {regional_date}")
# identify if any special event
headwrap_div = soup.find('div', {'class': 'dpPHeaderWrapper'})
event_div = headwrap_div.find('div', {'class': 'dpPHeaderEventList'})
if event_div:
event_text = event_div.get_text(separator=' ', strip=True)
else:
event_text = None
# result
date_object = {}
date_object['method'] = self.method
date_object['language'] = (
self.methods[self.method]['language']
if self.regional_language else
'English'
)
date_object['city'] = self.city
date_object['ce_date'] = ce_date
date_object['ce_datestring'] = ce_datestring
date_object['event'] = event_text
date_object['regional_date'] = regional_date
date_object['regional_datestring'] = regional_datestring
date_object['panchang'] = panchang
date_object['url'] = date_url
return date_object
def get_regional_lists(self):
r = self.get(self.method_url)
content = r.content.decode('utf-8')
soup = BeautifulSoup(content, 'html.parser')
lists_div = soup.find('div', {'class': 'dpListsWrapper'})
cards = lists_div.find_all('div', {'class': 'dpCard'})
regional_lists = {}
for card in cards:
header = card.find('h2', {'class': 'dpCardTitle'})
ol = card.find('ol', {'class': 'dpListContent'})
if ol and header:
header_text = header.get_text(separator=' ', strip=True)
items = ol.find_all('li')
elements = []
for item in items:
elements.append(item.get_text(separator=' ', strip=True))
pattern_map = {
'Month List': 'month_names',
'Nakshatra List': 'nakshatra_names',
'Anandadi Yoga Names': 'anandadi_yoga_names',
'Yoga Names': 'yoga_names',
'Karana Names': 'karana_names',
'Tithi Names': 'tithi_names',
'Zodiac Names': 'zodiac_names',
'Samvatsara Names': 'samvatsara_names'
}
for pattern, varname in pattern_map.items():
if pattern in header_text:
header_text = varname
break
else:
logging.warning(f"Unknown Header: {header_text}")
if header_text in regional_lists:
logging.error(f"List '{header_text}' overwritten.")
regional_lists[header_text] = elements
self.regional_lists = regional_lists
if self.storage_dir:
for known_header in pattern_map.values():
if known_header in regional_lists:
list_filename = f'{self.method}_{known_header}.txt'
list_file = os.path.join(self.storage_dir, list_filename)
with open(list_file, 'w') as f:
f.write('\n'.join(regional_lists[known_header]))
return regional_lists
def find_regional_date(self, date):
"""Wrapper for finding the CE date for a regional date (Tithi)"""
return self.get_date(date, regional=True)
def find_occurrence(self, date, year=None, regional=True):
"""
Find future or past occurrences of the given date
Example:
>>> calendar = HinduCalendar()
>>> Obj.find_occurrence('23/05/1911', year=2020)
This will return the date in the year 2020, which has regional date
'23/05/1911' which corresponds to Shravana, Krishna Paksha, Ashtami.
>>> Obj.find_occurrence('15/08/1947', regional=False, year=2020)
This will return the date which has the same regional date (Tithi)
in the CE year 2020, as that on '15/08/1947'.
Parameters
----------
date : str
Date in dd/mm/yyyy format
year : str, optional
Find the occurrence of the given date in the given CE year.
If None, the next occurrence will be found.
The default is None.
regional : bool, optional
True, if input date is a regional date.
False, if the input date is CE date.
The default is True.
Returns
-------
date_obj
Date object, as returned by self.get_date()
"""
logging.info(f"get_date(date={date}, regional={regional}, "
f"year={year}")
date_obj = self.get_date(date, regional=regional)
dd, mm, yyyy = date_obj['regional_date'].split('/')
ce_dd, ce_mm, ce_yyyy = date_obj['ce_date'].split('/')
if year is not None:
year_diff = int(year) - int(ce_yyyy)
else:
year_diff = 1
yyyy = int(yyyy) + year_diff
date = f'{dd}/{mm}/{yyyy}'
date_obj = self.find_regional_date(date)
_, _, future_year = date_obj['ce_date'].split('/')
year_diff = int(future_year) - int(year)
if year_diff:
logging.info("Correction applied.")
yyyy = int(yyyy) + year_diff
date_obj = self.find_regional_date(date)
return date_obj
def find_reoccurrence(self, date, year=None):
"""
Wrapper to find re-occurrence of 'Tithi' of the CE 'date' in the 'year'
If 'year' is None, current year is used.
"""
if year is None:
year = dt.today().year
return self.find_occurrence(date, regional=False, year=year)
def set_city(self, geonames_id, city):
logging.info(f"City change: {(self.geonames_id, self.city)} -> "
f"{(geonames_id, city)}")
self.geonames_id = geonames_id
self.city = city
def set_regional_language(self, regional):
if regional:
language = 'regional'
else:
language = 'english'
self.regional_language = regional
if language == 'regional':
lang = self.methods[self.method]['lang']
numeral = 'regional'
else:
lang = 'en'
numeral = 'english'
language_cookies = [
requests.cookies.create_cookie(
domain='.drikpanchang.com',
name='dkplanguage',
value=lang
),
requests.cookies.create_cookie(
domain='.drikpanchang.com',
name='dkpnumerallocale',
value=numeral
)
]
self.set_cookies(*language_cookies)
return True
def set_method(self, method):
logging.info(f"Method change: '{self.method}' -> '{method}'")
valid_methods = list(self.methods.keys())
method = method.strip().lower()
if method in valid_methods:
self.method = method
self.method_url = self.get_url(method)
self.method_day_url = self.get_url(method, day=True)
self.set_regional_language(self.regional_language)
else:
if method not in self.methods:
raise RuntimeWarning("Invalid method. "
f"Valid methods are: {valid_methods}")
return False
return True
def set_cookies(self, *cookie_objs):
'''
Set cookies for the session
Parameters
----------
*cookie_objs : object
List of valid cookie objects,
created by the method 'requests.cookies.create_cookie()'
'''
for cookie_obj in cookie_objs:
self._session.cookies.set_cookie(cookie_obj)
def get_url(self, key, day=False):
if key in self.sitemap:
return f'{self.server_url}{self.sitemap[key]}'
if key in self.methods:
cal_type = 'day' if day else 'month'
return f"{self.server_url}{self.methods[key][cal_type]}"
def today(self):
'''Find today's regional date'''
today = dt.now().strftime("%d/%m/%Y")
return self.get_date(today)
def get(self, *args, **kwargs):
return self._session.get(*args, **kwargs)
def post(self, *args, **kwargs):
return self._session.post(*args, **kwargs)
@staticmethod
def find_city(city, n=None):
"""
Find city and its GeoNames ID.
Show at most 'n' options. (n=None will show all options)
"""
_parse = urlparse('https://www.drikpanchang.com/')
_path = 'placeholder/ajax/geo/dp-city-search.php'
_query = f'search={city}'
city_search_url = _parse._replace(path=_path, query=_query).geturl()
r = requests.get(city_search_url)
content = r.content.decode('utf-8')
result = json.loads(content)['geonames']
if n is not None:
return result[:n]
return result
def __repr__(self):
lang = (
self.methods[self.method]['lang']
if self.regional_language else
'en'
)
return (f"Calendar(method={self.method}, "
f"lang={lang}, city={self.city})")
###############################################################################
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Wed Feb 5 22:01:32 2020
Updated on Thu Aug 27 14:45:32 2020
Updated on Tue Jan 31 12:06:09 2022
@author: Hrishikesh Terdalkar
Create ICS by plotting the Tithis of Birthdays/Anniversaries on CE calendar
Input: CSV file containing CE birthdays/anniversaries
Output: ICS file importable in Google Calendar
Third-party Dependancies:
ics
"""
import os
import argparse
from datetime import datetime as dt
from ics import Calendar, Event
from ics.grammar.parse import ContentLine
from hindu_calendar import HinduCalendar
###############################################################################
def main():
home_dir = os.path.expanduser('~')
default_input_file = 'events.csv'
default_output_file = 'events.ics'
desc = 'Create Hindu Calendar ICS'
p = argparse.ArgumentParser(description=desc)
p.add_argument("-i", help="Input CSV file", default=default_input_file)
p.add_argument("-o", help="Output ICS file", default=default_output_file)
p.add_argument("-y", help="Year", default=dt.now().year)
args = vars(p.parse_args())
input_file = args['i']
output_file = args['o']
year = args['y']
# CSV file contains header
header = True
with open(input_file, 'r') as f:
event_list = [[word.strip() for word in line.strip().split(',')]
for line in f.read().split('\n') if line.strip(", ")]
if header:
event_list.pop(0)
calendar = Calendar()
calendar.creator = ("HinduCalendar using drikPanchang.com "
"by Hrishikesh Terdalkar")
calendar.extra.append(ContentLine(name='CALSCALE', value='GREGORIAN'))
for event_row in event_list:
date, regional_date, label, name, method, use_regional, include = event_row
include = bool(int(include))
if not include:
continue
use_regional = bool(int(use_regional))
if use_regional:
date = regional_date
hindu_calendar = HinduCalendar(method=method)
date_obj = hindu_calendar.find_occurrence(date,
year=year,
regional=use_regional)
current_occurence = dt.strptime(date_obj['ce_date'], "%d/%m/%Y")
event_description = [date_obj['regional_datestring']]
if date_obj['event']:
event_description.append(f"** {date_obj['event']} **")
for key, value in date_obj['panchang'].items():
if key.lower().startswith('tith'):
event_description.append(f"({value})")
break
e = Event()
e.name = f"{name}'s {label}"
e.begin = current_occurence.isoformat()
e.make_all_day()
e.description = '\n'.join(event_description)
calendar.events.add(e)
with open(output_file, 'w') as f:
f.write(str(calendar))
return locals()
###############################################################################
if __name__ == '__main__':
locals().update(main())
@vishalparkar
Copy link

@madhavramkumar, I hope that @hrishikeshrt could take some time out to look into this and help here.

@parthjaggi
Copy link

For fixing the above issue replace the below code:

        for script in scripts:
            # TODO: Find a better way than str(script)
            # BeautifulSoup4 >= 4.9 stopped extracting text from script tags
            script_text = str(script)
            if 'var dpRegionalMonthList' in script_text:
                regional_str = script_text.split(';')[-2].split('=')[1].strip()
                regional_months = json.loads(regional_str)

With

        for script in scripts:
            # TODO: Find a better way than str(script)
            # BeautifulSoup4 >= 4.9 stopped extracting text from script tags
            script_text = str(script)
            string_to_find = "dpTimeContext.localized_regional_month_list_"
            if string_to_find in script_text:
                regional_str_match = next((t for t in script_text.split(";") if string_to_find in t), None)
                regional_str = regional_str_match.split("=")[1].strip()
                regional_months = json.loads(regional_str)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment