Python实现好友生日提醒

前言

之前有一次忘记了好友的生日,一直觉得过意不去。之前把亲友的生日都直接写在备忘录里面,一个个的设置在手机的日期提醒里面有些麻烦,于是写一个程序来提醒我,提醒方式是发邮件。

步骤

思考了一下,有几个难点:

  • 统一备忘录格式
  • 我的生日备忘录里面,记录有亲友的农历生日和公历生日,农历和公历要相互转换。
  • 发邮件
  • 要找个服务器定时执行python脚本。

一个一个的来解决。

统一备忘录格式

把备忘录文本复制到一个csv文件,birthday.csv

birthday.csv
张三,公历,4.15
李四,农历,3.23
王五,农历,3.10
赵六,农历,3.10

农历和公历转换

没有找到python好用的库,找到一段程序,亲测有效。

LunarSolarConverter.py

# -*- coding: utf-8 -*-
from pprint import pprint


class Lunar:
    def __init__(self, lunarYear, lunarMonth, lunarDay, isleap):
        self.isleap = isleap
        self.lunarDay = lunarDay
        self.lunarMonth = lunarMonth
        self.lunarYear = lunarYear


class Solar:
    def __init__(self, solarYear, solarMonth, solarDay):
        self.solarDay = solarDay
        self.solarMonth = solarMonth
        self.solarYear = solarYear


def GetBitInt(data, length, shift):
    return (data & (((1 << length) - 1) << shift)) >> shift


def SolarToInt(y, m, d):
    m = (m + 9) % 12
    y -= m // 10
    return 365 * y + y // 4 - y // 100 + y // 400 + (m * 306 + 5) // 10 + (d - 1)


def SolarFromInt(g):
    y = (10000 * g + 14780) // 3652425
    ddd = g - (365 * y + y // 4 - y // 100 + y // 400)
    if ddd < 0:
        y -= 1
        ddd = g - (365 * y + y // 4 - y // 100 + y // 400)
    mi = (100 * ddd + 52) // 3060
    mm = (mi + 2) % 12 + 1
    y += (mi + 2) // 12
    dd = ddd - (mi * 306 + 5) // 10 + 1
    solar = Solar(y, mm, dd)
    return solar


class LunarSolarConverter:
    lunar_month_days = [1887, 0x1694, 0x16aa, 0x4ad5, 0xab6, 0xc4b7, 0x4ae, 0xa56, 0xb52a,
                        0x1d2a, 0xd54, 0x75aa, 0x156a, 0x1096d, 0x95c, 0x14ae, 0xaa4d, 0x1a4c, 0x1b2a, 0x8d55,
                        0xad4, 0x135a, 0x495d,
                        0x95c, 0xd49b, 0x149a, 0x1a4a, 0xbaa5, 0x16a8, 0x1ad4, 0x52da, 0x12b6, 0xe937, 0x92e,
                        0x1496, 0xb64b, 0xd4a,
                        0xda8, 0x95b5, 0x56c, 0x12ae, 0x492f, 0x92e, 0xcc96, 0x1a94, 0x1d4a, 0xada9, 0xb5a, 0x56c,
                        0x726e, 0x125c,
                        0xf92d, 0x192a, 0x1a94, 0xdb4a, 0x16aa, 0xad4, 0x955b, 0x4ba, 0x125a, 0x592b, 0x152a,
                        0xf695, 0xd94, 0x16aa,
                        0xaab5, 0x9b4, 0x14b6, 0x6a57, 0xa56, 0x1152a, 0x1d2a, 0xd54, 0xd5aa, 0x156a, 0x96c,
                        0x94ae, 0x14ae, 0xa4c,
                        0x7d26, 0x1b2a, 0xeb55, 0xad4, 0x12da, 0xa95d, 0x95a, 0x149a, 0x9a4d, 0x1a4a, 0x11aa5,
                        0x16a8, 0x16d4,
                        0xd2da, 0x12b6, 0x936, 0x9497, 0x1496, 0x1564b, 0xd4a, 0xda8, 0xd5b4, 0x156c, 0x12ae,
                        0xa92f, 0x92e, 0xc96,
                        0x6d4a, 0x1d4a, 0x10d65, 0xb58, 0x156c, 0xb26d, 0x125c, 0x192c, 0x9a95, 0x1a94, 0x1b4a,
                        0x4b55, 0xad4,
                        0xf55b, 0x4ba, 0x125a, 0xb92b, 0x152a, 0x1694, 0x96aa, 0x15aa, 0x12ab5, 0x974, 0x14b6,
                        0xca57, 0xa56, 0x1526,
                        0x8e95, 0xd54, 0x15aa, 0x49b5, 0x96c, 0xd4ae, 0x149c, 0x1a4c, 0xbd26, 0x1aa6, 0xb54,
                        0x6d6a, 0x12da, 0x1695d,
                        0x95a, 0x149a, 0xda4b, 0x1a4a, 0x1aa4, 0xbb54, 0x16b4, 0xada, 0x495b, 0x936, 0xf497,
                        0x1496, 0x154a, 0xb6a5,
                        0xda4, 0x15b4, 0x6ab6, 0x126e, 0x1092f, 0x92e, 0xc96, 0xcd4a, 0x1d4a, 0xd64, 0x956c,
                        0x155c, 0x125c, 0x792e,
                        0x192c, 0xfa95, 0x1a94, 0x1b4a, 0xab55, 0xad4, 0x14da, 0x8a5d, 0xa5a, 0x1152b, 0x152a,
                        0x1694, 0xd6aa,
                        0x15aa, 0xab4, 0x94ba, 0x14b6, 0xa56, 0x7527, 0xd26, 0xee53, 0xd54, 0x15aa, 0xa9b5, 0x96c,
                        0x14ae, 0x8a4e,
                        0x1a4c, 0x11d26, 0x1aa4, 0x1b54, 0xcd6a, 0xada, 0x95c, 0x949d, 0x149a, 0x1a2a, 0x5b25,
                        0x1aa4, 0xfb52,
                        0x16b4, 0xaba, 0xa95b, 0x936, 0x1496, 0x9a4b, 0x154a, 0x136a5, 0xda4, 0x15ac]

    solar_1_1 = [1887, 0xec04c, 0xec23f, 0xec435, 0xec649, 0xec83e, 0xeca51, 0xecc46, 0xece3a,
                 0xed04d, 0xed242, 0xed436, 0xed64a, 0xed83f, 0xeda53, 0xedc48, 0xede3d, 0xee050, 0xee244, 0xee439,
                 0xee64d,
                 0xee842, 0xeea36, 0xeec4a, 0xeee3e, 0xef052, 0xef246, 0xef43a, 0xef64e, 0xef843, 0xefa37, 0xefc4b,
                 0xefe41,
                 0xf0054, 0xf0248, 0xf043c, 0xf0650, 0xf0845, 0xf0a38, 0xf0c4d, 0xf0e42, 0xf1037, 0xf124a, 0xf143e,
                 0xf1651,
                 0xf1846, 0xf1a3a, 0xf1c4e, 0xf1e44, 0xf2038, 0xf224b, 0xf243f, 0xf2653, 0xf2848, 0xf2a3b, 0xf2c4f,
                 0xf2e45,
                 0xf3039, 0xf324d, 0xf3442, 0xf3636, 0xf384a, 0xf3a3d, 0xf3c51, 0xf3e46, 0xf403b, 0xf424e, 0xf4443,
                 0xf4638,
                 0xf484c, 0xf4a3f, 0xf4c52, 0xf4e48, 0xf503c, 0xf524f, 0xf5445, 0xf5639, 0xf584d, 0xf5a42, 0xf5c35,
                 0xf5e49,
                 0xf603e, 0xf6251, 0xf6446, 0xf663b, 0xf684f, 0xf6a43, 0xf6c37, 0xf6e4b, 0xf703f, 0xf7252, 0xf7447,
                 0xf763c,
                 0xf7850, 0xf7a45, 0xf7c39, 0xf7e4d, 0xf8042, 0xf8254, 0xf8449, 0xf863d, 0xf8851, 0xf8a46, 0xf8c3b,
                 0xf8e4f,
                 0xf9044, 0xf9237, 0xf944a, 0xf963f, 0xf9853, 0xf9a47, 0xf9c3c, 0xf9e50, 0xfa045, 0xfa238, 0xfa44c,
                 0xfa641,
                 0xfa836, 0xfaa49, 0xfac3d, 0xfae52, 0xfb047, 0xfb23a, 0xfb44e, 0xfb643, 0xfb837, 0xfba4a, 0xfbc3f,
                 0xfbe53,
                 0xfc048, 0xfc23c, 0xfc450, 0xfc645, 0xfc839, 0xfca4c, 0xfcc41, 0xfce36, 0xfd04a, 0xfd23d, 0xfd451,
                 0xfd646,
                 0xfd83a, 0xfda4d, 0xfdc43, 0xfde37, 0xfe04b, 0xfe23f, 0xfe453, 0xfe648, 0xfe83c, 0xfea4f, 0xfec44,
                 0xfee38,
                 0xff04c, 0xff241, 0xff436, 0xff64a, 0xff83e, 0xffa51, 0xffc46, 0xffe3a, 0x10004e, 0x100242,
                 0x100437,
                 0x10064b, 0x100841, 0x100a53, 0x100c48, 0x100e3c, 0x10104f, 0x101244, 0x101438, 0x10164c,
                 0x101842, 0x101a35,
                 0x101c49, 0x101e3d, 0x102051, 0x102245, 0x10243a, 0x10264e, 0x102843, 0x102a37, 0x102c4b,
                 0x102e3f, 0x103053,
                 0x103247, 0x10343b, 0x10364f, 0x103845, 0x103a38, 0x103c4c, 0x103e42, 0x104036, 0x104249,
                 0x10443d, 0x104651,
                 0x104846, 0x104a3a, 0x104c4e, 0x104e43, 0x105038, 0x10524a, 0x10543e, 0x105652, 0x105847,
                 0x105a3b, 0x105c4f,
                 0x105e45, 0x106039, 0x10624c, 0x106441, 0x106635, 0x106849, 0x106a3d, 0x106c51, 0x106e47,
                 0x10703c, 0x10724f,
                 0x107444, 0x107638, 0x10784c, 0x107a3f, 0x107c53, 0x107e48]

    # 月历转太阳历
    def LunarToSolar(self, lunar):
        days = LunarSolarConverter.lunar_month_days[lunar.lunarYear - LunarSolarConverter.lunar_month_days[0]]
        leap = GetBitInt(days, 4, 13)
        offset = 0
        loopend = leap
        if not lunar.isleap:

            if lunar.lunarMonth <= leap or leap == 0:

                loopend = lunar.lunarMonth - 1

            else:

                loopend = lunar.lunarMonth

        for i in range(0, loopend):
            offset += GetBitInt(days, 1, 12 - i) == 1 and 30 or 29

        offset += lunar.lunarDay

        solar11 = LunarSolarConverter.solar_1_1[lunar.lunarYear - LunarSolarConverter.solar_1_1[0]]

        y = GetBitInt(solar11, 12, 9)
        m = GetBitInt(solar11, 4, 5)
        d = GetBitInt(solar11, 5, 0)

        return SolarFromInt(SolarToInt(y, m, d) + offset - 1)


    # 太阳历转月历
    def SolarToLunar(self, solar):

        lunar = Lunar(0, 0, 0, False)
        index = solar.solarYear - LunarSolarConverter.solar_1_1[0]
        data = (solar.solarYear << 9) | (solar.solarMonth << 5) | solar.solarDay
        if LunarSolarConverter.solar_1_1[index] > data:
            index -= 1

        solar11 = LunarSolarConverter.solar_1_1[index]
        y = GetBitInt(solar11, 12, 9)
        m = GetBitInt(solar11, 4, 5)
        d = GetBitInt(solar11, 5, 0)
        offset = SolarToInt(solar.solarYear, solar.solarMonth, solar.solarDay) - SolarToInt(y, m, d)

        days = LunarSolarConverter.lunar_month_days[index]
        leap = GetBitInt(days, 4, 13)

        lunarY = index + LunarSolarConverter.solar_1_1[0]
        lunarM = 1
        offset += 1

        for i in range(0, 13):

            dm = GetBitInt(days, 1, 12 - i) == 1 and 30 or 29
            if offset > dm:

                lunarM += 1
                offset -= dm

            else:

                break

        lunarD = int(offset)
        lunar.lunarYear = lunarY
        lunar.lunarMonth = lunarM
        lunar.isleap = False
        if leap != 0 and lunarM > leap:

            lunar.lunarMonth = lunarM - 1
            if lunarM == leap + 1:
                lunar.isleap = True

        lunar.lunarDay = lunarD
        return lunar

    def __init__(self):
        pass


if __name__ == '__main__':
    converter = LunarSolarConverter()
    solar = Solar(2020, 4, 14)
    pprint(vars(solar))
    lunar = converter.SolarToLunar(solar)
    pprint(vars(lunar))
    solar = converter.LunarToSolar(lunar)
    pprint(vars(solar))

    lunar = Lunar(2020, 3, 22, isleap=False)
    pprint(vars(lunar))
    solar = converter.LunarToSolar(lunar)
    pprint(vars(solar))
    lunar = converter.SolarToLunar(solar)
    pprint(vars(lunar))

    print("Done")

发邮件

发邮件分两步,一是填写邮件标题和内容等信息,二是通过smtp协议发送邮件,这两个都有现成的python库
qq.py

# -*- coding: utf-8 -*-

import smtplib
from email.mime.text import MIMEText


def sendMail(title: str, msg: str):
    # 设置服务器所需信息
    # qq邮箱服务器地址
    mail_host = 'smtp.qq.com'
    # qq用户名
    mail_user = '[email protected]'
    # 授权码
    mail_pass = 'xxxxxxxxxxxxx'
    # 发送方邮箱地址
    sender = '[email protected]'
    # 接收方邮件地址
    receivers = ['[email protected]']

    # 设置email信息
    # 邮件内容设置
    message = MIMEText(msg,'plain','utf-8')
    # 邮件主题
    message['Subject'] = title
    # 发送方信息
    message['From'] = sender
    # 接受方信息
    message['To'] = receivers[0]

    # 登录并发送邮件
    try:
        smtpObj = smtplib.SMTP()
        # 连接到服务器
        smtpObj.connect(mail_host, 25)
        # 登录到服务器
        smtpObj.login(mail_user, mail_pass)
        # 发送
        smtpObj.sendmail(
            sender, receivers, message.as_string())
        # 退出
        smtpObj.quit()
        print('邮件发送成功')
    except smtplib.SMTPException as e:
        print('邮件发送失败', e)

整合程序

上面已经准备好了qq.py和LunarSolarConverter.py两个文件分别用于发邮件和阴阳历转换,接下来就是写逻辑代码整合了

# -*- coding: utf-8 -*-

from qq import sendMail
import csv
import datetime
from LunarSolarConverter import LunarSolarConverter, Solar, Lunar

now = datetime.datetime.now()
print('公历-----------------------------------')
print('日期时间: ', now)

solar_year = now.year
solar_month = now.month
solar_day = now.day
print('年: ', solar_year)
print('月: ', solar_month)
print('日: ', solar_day)
print('\n')

print('阴历-----------------------------------')
converter = LunarSolarConverter()
solar = Solar(solar_year, solar_month, solar_day)
lunar = converter.SolarToLunar(solar)

print('日期: ', vars(lunar))
print('\n')

with open('birthday.csv') as csv_file:
    csv_reader = csv.reader(csv_file, delimiter=',')
    line_count = 0

    for row in csv_reader:
        name = row[0]
        date_type = row[1]
        birthday = row[2]
        month = int(birthday.split('.')[0])
        day = int(birthday.split('.')[1])

        print(f'\t{name} {date_type} {birthday}')

        if (date_type == '农历' and month == lunar.lunarMonth and day == lunar.lunarDay) or (date_type == '公历' and month == solar_month and day == solar_day):
            title = '亲友生日提醒'
            msg = f'请注意! 这两天是{name}的{date_type}生日, 具体日期是{date_type} {birthday}'
            sendMail(title, msg)

        line_count += 1

    print(f'Processed {line_count} lines.')

定时执行脚本

专门弄台服务器或者云服务器来跑脚本有点浪费,想到了可以用腾讯的云函数来搞。不了解腾讯云函数可以先看看介绍:
https://cloud.tencent.com/product/scf

腾讯云函数,免费开通之后每个月有定量的免费额度:


腾讯云函数

像生日提醒程序的话,定时任务每天调用一次即可。

新建云函数

选择模板

我这里提示函数名称已存在,因为在写博文的时候,这个函数已经建好了。

编辑脚本

选择好模板之后下一步就是把之前本地写好的代码复制过来

代码编辑

注意,需要把之前本地的执行入口移到index.py的main_handler方法下面,因为执行方法指定了入口:


执行入口

设置触发方式

触发方式

我这里选择的是定时触发,每天触发一次。

每日触发

当然,为了快速测试一下程序是否正常,可以临时设置为每分钟执行一次,然后看下云函数的执行日志。

执行日志

日志
日志

查看邮件

邮件列表
亲友生日提醒
亲友生日提醒

结束语

到此一步,就算是把这个好友生日提醒程序给完成了,接下来就等着收邮件,亲友的生日一定不能忘记哦!

参考文章

使用 Python 读写 CSV 文件(一)
使用 Python 读写 CSV 文件(二)
简单三步,用 Python 发邮件
Python 农历公历算法转换
python工具代码之农历转换公历,公历转换农历神器持续更新
https://www.cnblogs.com/bovenson/p/6604803.html
https://www.v2ex.com/t/332756
https://cloud.tencent.com/product/scf/pricing

你可能感兴趣的:(Python实现好友生日提醒)