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 = 'xxxxxxxxxxx@qq.com'
    # 授权码
    mail_pass = 'xxxxxxxxxxxxx'
    # 发送方邮箱地址
    sender = 'xxxxxxxxxxx@qq.com'
    # 接收方邮件地址
    receivers = ['xxxxxxxxxxx@qq.com']

    # 设置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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357

推荐阅读更多精彩内容