doupoa
一个不甘落后的热血青年!
Ping通途说

Py有事没事[1] - 实现钱条功能

文章目录[隐藏]

0. 前言

这个是我最近决定开设的栏目,用于记录一些突发奇想,或者闲来无事,用Python开发制作的小工具小游戏,希望能在练习中提升一下自己!

2025/09/17 更新:
keyboard_arrow_down

1. 修复了打印闪屏的问题 2. 增加日薪计算功能 3. 新增了彩色打印


1. 概念

拿着月薪都在想日薪多少多少,但那些大佬就不一样了,用秒来计算每秒挣了多少钱!

那我们能不能在Python中实现一下?

2. 开发

先贴代码:
Python
############ 配置区 ############
import chinese_calendar as holiday  # 判断是否为中国节假日 盈透版本2025
from datetime import datetime, timedelta
import time
import os
import base64
import ctypes

entry = "2025/07/20"  # 入职日期
mySalary = b'Mjg2MA=='  # 薪资 base64编码格式
workTime = "09:30"  # 上班时间
closeTime = "18:30"  # 下班时间
myHoliday = 8  # 月休息天数 考虑大小月
dailySalary = 130  # 日薪,如果设置则按日薪计算,格式为数字或 None
###############################


# 添加颜色输出支持

class Colors:
    HEADER = '\033[95m'
    OKBLUE = '\033[94m'
    OKCYAN = '\033[96m'
    OKGREEN = '\033[92m'
    WARNING = '\033[93m'
    FAIL = '\033[91m'
    ENDC = '\033[0m'
    BOLD = '\033[1m'
    UNDERLINE = '\033[4m'


# 光标控制类
if os.name == 'nt':
    class _CursorInfo(ctypes.Structure):
        _fields_ = [("size", ctypes.c_int),
                    ("visible", ctypes.c_byte)]

def hide_cursor() -> None:
    """隐藏控制台光标"""
    if os.name == 'nt':
        ci = _CursorInfo()
        handle = ctypes.windll.kernel32.GetStdHandle(-11)
        ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
        ci.visible = False
        ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
    else:
        print('\033[?25l', end='')

def show_cursor() -> None:
    """显示控制台光标"""
    if os.name == 'nt':
        ci = _CursorInfo()
        handle = ctypes.windll.kernel32.GetStdHandle(-11)
        ctypes.windll.kernel32.GetConsoleCursorInfo(handle, ctypes.byref(ci))
        ci.visible = True
        ctypes.windll.kernel32.SetConsoleCursorInfo(handle, ctypes.byref(ci))
    else:
        print('\033[?25h', end='')


today = datetime.now()
MINIMUM_WAGE = 22.2  # 广州最低时薪


class TimeFormat:
    def sec2Text(self, sec: int) -> str:
        """将秒数转换为可读的时间文本"""
        m, s = divmod(sec, 60)
        h, m = divmod(m, 60)
        return "%02d小时%02d分钟%02d" % (h, m, s)

    def time2Sec(self, time_str: str) -> int:
        """将时间字符串转换为秒数"""
        h, m, s = map(int, time_str.split(":"))
        return h * 3600 + m * 60 + s

    def getMonthDays(self, year: int, month: int) -> int:
        """获取指定年月的天数"""
        if month in [1, 3, 5, 7, 8, 10, 12]:
            return 31
        elif month in [4, 6, 9, 11]:
            return 30
        elif (year % 4 == 0 and year % 100 != 0) or year % 400 == 0:
            return 29
        else:
            return 28

    def whatDay(self, date: datetime) -> dict:
        """判断日期类型"""
        if holiday.is_workday(date.date()):
            return {"type": 1, "name": "工作日"}
        elif holiday.is_holiday(date.date()):
            return {"type": 2, "name": "休息日"}
        else:
            return {"type": 1, "name": "工作日"}  # 默认为工作日


def welcome() -> None:
    """显示欢迎信息"""
    delta = today - datetime.strptime(entry, "%Y/%m/%d")

    def time2Text() -> str:
        if today.hour < 6:
            return "凌晨好"
        if 6 <= today.hour < 12:
            return "早上好"
        if 12 <= today.hour < 18:
            return "下午好"
        if 18 <= today.hour < 24:
            return "晚上好"

    msg = rf"""
   ___   ___    __ _____ _____ _    _
  / _ \ / _ \  / /|_   _/ ____| |  | |
 | (_) | (_) |/ /_  | || |    | |  | |
  \__, |\__, | '_ \ | || |    | |  | |
    / /   / /| (_) || || |____| |__| |
   /_/   /_/  \___/_____\_____|\____/

{Colors.OKCYAN}{time2Text()}{Colors.ENDC}
{Colors.OKGREEN}今天是你上班的第{delta.days}{Colors.ENDC}"""
    print(msg)
    time.sleep(3)
    # 使用 ANSI 转义序列清屏而不是 cls 命令
    print('\033[2J\033[H', end='')


def calculate_overtime_pay(wage: float, overtime_seconds: int, day_type: int) -> tuple:
    """计算加班费"""
    overtime_hours = overtime_seconds / 3600
    base_pay = (MINIMUM_WAGE if wage < MINIMUM_WAGE else wage) * overtime_hours

    if day_type == 1:  # 工作日
        return base_pay * 1.5, "工作日加班费"
    elif day_type == 2:  # 休息日
        return base_pay * 2, "休息日加班费"
    else:
        return base_pay * 1.5, "加班费"  # 默认工作日


def loop() -> None:
    """主循环"""
    tool = TimeFormat()
    salary = int(base64.b64decode(mySalary).decode("utf-8"))  # 薪资
    start = timedelta(hours=int(workTime.split(
        ":")[0]), minutes=int(workTime.split(":")[1]))  # 上班时间
    end = timedelta(hours=int(closeTime.split(
        ":")[0]), minutes=int(closeTime.split(":")[1]))  # 下班时间
    offwork = False  # 下班状态
    last_overtime_display = 0  # 上次显示加班信息的时间

    # 用于计算需要清除的行数
    previous_lines = 0

    while True:
        now = datetime.now()  # 当前时间
        # 时薪:如果设置了日薪,则按日薪计算;否则按月工资收入÷(月计薪天数×8小时)计算
        if dailySalary is not None:
            myWage = dailySalary / 8  # 假设每天工作8小时
        else:
            myWage = salary/((tool.getMonthDays(now.year, now.month)-myHoliday)*8)
        
        deltaNow = timedelta(
            hours=now.hour, minutes=now.minute, seconds=now.second)
        schedule = deltaNow - start  # 当前进度
        
        # 今日应得:如果设置了日薪,则按日薪计算;否则按月工资计算
        if dailySalary is not None:
            todayGet = dailySalary
        else:
            todayGet = salary/tool.getMonthDays(now.year, now.month)
            
        todayProcess = schedule.seconds / \
            (end-start).seconds if (end-start).seconds > 0 else 0  # 今日进度

        if deltaNow >= start and deltaNow < end:  # 上班时间内
            progress_bar_length = 30
            filled_length = int(progress_bar_length * todayProcess)
            bar = '' * filled_length + '-' * \
                (progress_bar_length - filled_length)

            msg = f"""
{Colors.OKBLUE}当前时间:{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}{Colors.ENDC}

{Colors.OKGREEN}你已经上了:{(tool.sec2Text(schedule.seconds))}{Colors.ENDC}

{Colors.WARNING}还有 {tool.sec2Text((end-deltaNow).seconds)} 下班{Colors.ENDC}

今日工作进度: |{Colors.OKGREEN}{bar}{Colors.ENDC}| {todayProcess*100:.1f}%
今天已经挣了: {Colors.OKGREEN}{format(todayProcess*todayGet, '.2f')}{Colors.ENDC}
还需努力挣: {Colors.WARNING}{format((1-todayProcess)*todayGet, '.2f')}{Colors.ENDC}元"""
            
            # 计算当前消息的行数
            current_lines = msg.count('\n')
            
            # 清除之前的内容
            if previous_lines > 0:
                print(f'\033[{previous_lines}A\033[2K', end='')
                for _ in range(current_lines - 1):
                    print('\033[2K\033[B', end='')
                print(f'\033[{current_lines}A\033[2K', end='')
            
            # 更新之前行数
            previous_lines = current_lines
            
            # 打印新内容
            print(msg)

            try:
                time.sleep(1)
            except KeyboardInterrupt:
                print(f"\n{Colors.OKBLUE}再见!{Colors.ENDC}")
                show_cursor()
                exit()

        elif deltaNow >= end:
            dayType = tool.whatDay(now)
            if not offwork:
                offwork = True
                msg = f"""
{Colors.OKGREEN}      ____  ______ ________          ______  _____  _  __
     / __ \|  ____|  ____\ \        / / __ \|  __ \| |/ /
    | |  | | |__  | |__   \ \  /\  / / |  | | |__) | ' /
    | |  | |  __| |  __|   \ \/  \/ /| |  | |  _  /|  <
    | |__| | |    | |       \  /\  / | |__| | | \ \| . \\
     \____/|_|    |_|        \/  \/   \____/|_|  \_\_|\_\\
{Colors.ENDC}
{Colors.OKCYAN}                            下班啦!{Colors.ENDC}
    """
                # 计算当前消息的行数
                current_lines = msg.count('\n')
                
                # 清除之前的内容
                if previous_lines > 0:
                    print(f'\033[{previous_lines}A\033[2K', end='')
                    for _ in range(current_lines - 1):
                        print('\033[2K\033[B', end='')
                    print(f'\033[{current_lines}A\033[2K', end='')
                
                # 更新之前行数
                previous_lines = current_lines
                
                # 打印新内容
                print(msg)
                try:
                    time.sleep(3)
                except KeyboardInterrupt:
                    print(f"\n{Colors.OKBLUE}再见!{Colors.ENDC}")
                    show_cursor()
                    exit()
            else:
                msg = f"""
{Colors.OKBLUE}当前时间:{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}{Colors.ENDC}

{Colors.OKGREEN}你已经上了:{(tool.sec2Text(schedule.seconds))}{Colors.ENDC}

{Colors.WARNING}已经下班 {tool.sec2Text(deltaNow.seconds - end.seconds)} 啦!{Colors.ENDC}

今天是:{Colors.OKCYAN}{dayType['name']}{Colors.ENDC}
"""
                overtime = ((MINIMUM_WAGE if myWage < MINIMUM_WAGE else myWage) /
                            3600)*(deltaNow.seconds-end.seconds)

                if dayType['type'] == 1:
                    overtime_pay, overtime_type = calculate_overtime_pay(
                        myWage, deltaNow.seconds - end.seconds, dayType['type'])
                    msg += f"\n{Colors.OKGREEN}{overtime_type}{format(overtime_pay, '.2f')}{Colors.ENDC}"
                elif dayType['type'] == 2:
                    overtime_pay, overtime_type = calculate_overtime_pay(
                        myWage, deltaNow.seconds - end.seconds, dayType['type'])
                    msg += f"{Colors.OKGREEN}{overtime_type}{format(overtime_pay, '.2f')}{Colors.ENDC}"
                    # 法定节假日加班费按3倍计算
                    statutory_overtime = overtime * 3
                    msg += f"\n\n{Colors.WARNING}法定节假日加班费:{format(statutory_overtime, '.2f')}{Colors.ENDC}"

                # 计算当前消息的行数
                current_lines = msg.count('\n')
                
                # 清除之前的内容
                if previous_lines > 0:
                    print(f'\033[{previous_lines}A\033[2K', end='')
                    for _ in range(current_lines - 1):
                        print('\033[2K\033[B', end='')
                    print(f'\033[{current_lines}A\033[2K', end='')
                
                # 更新之前行数
                previous_lines = current_lines
                
                # 打印新内容
                print(msg)
                try:
                    time.sleep(1)
                except KeyboardInterrupt:
                    print(f"\n{Colors.OKBLUE}再见!{Colors.ENDC}")
                    show_cursor()
                    exit()
        else:
            # 上班前时间
            time_until_work = (start - deltaNow).seconds
            msg = f"""
{Colors.OKBLUE}当前时间:{datetime.now().strftime("%Y/%m/%d %H:%M:%S")}{Colors.ENDC}

{Colors.WARNING}还未到上班时间{Colors.ENDC}

距离上班还有: {Colors.OKCYAN}{tool.sec2Text(time_until_work)}{Colors.ENDC}

今天是:{Colors.OKGREEN}{tool.whatDay(now)['name']}{Colors.ENDC}
"""
            # 计算当前消息的行数
            current_lines = msg.count('\n')
            
            # 清除之前的内容
            if previous_lines > 0:
                print(f'\033[{previous_lines}A\033[2K', end='')
                for _ in range(current_lines - 1):
                    print('\033[2K\033[B', end='')
                print(f'\033[{current_lines}A\033[2K', end='')
            
            # 更新之前行数
            previous_lines = current_lines
            
            # 打印新内容
            print(msg)

            try:
                time.sleep(1)
            except KeyboardInterrupt:
                print(f"\n{Colors.OKBLUE}再见!{Colors.ENDC}")
                show_cursor()
                exit()


def main() -> None:
    """主函数"""
    hide_cursor()  # 隐藏光标
    welcome()
    loop()


if __name__ == "__main__":
    try:
        main()
    finally:
        show_cursor()  # 显示光标
        # 清屏
        print('\033[2J\033[H', end='')

在代码中,我们实现了上班欢迎语,下班提示语,以及薪资的计算。

启动脚本后,我们就能看到今天是当牛马的第xx天?

也能根据当前时间判断早上好中午好晚上好。

其中,配置区我们配置了入职日期,base64编码的工资(以免有人看到心生嫉妒),上下班时间,以及月休息天。实际脚本运行中,还考虑了大小月(28天/30天/31天)的问题,让秒薪计算的更加精确。

欢迎语结束后实际运行效果如下:

真是越来越有盼头了!

下班后会有大横幅提示。

接下来重头戏!

脚本使用了chinese_calendar库判断当前是否为国内周末休息日、调休以及法定节假日,并按照设定的当地最低日薪计算当前的加班费。如果公司有加班费的相关补贴还好,不然就是实际亏损的薪资!?

(看看谁没有加班费~)

脚本所有功能介绍完毕,希望能帮到你。

0
0
赞赏

doupoa

文章作者

诶嘿

发表回复

textsms
account_circle
email

Ping通途说

Py有事没事[1] - 实现钱条功能
0. 前言 这个是我最近决定开设的栏目,用于记录一些突发奇想,或者闲来无事,用Python开发制作的小工具小游戏,希望能在练习中提升一下自己! 2025/09/17 更新:keyboard_arrow_dow…
扫描二维码继续阅读
2024-05-30

Optimized by WPJAM Basic