Pull to refresh

Comments 21

Думаю тут не помешала бы интеграция с одним из плагинов для диапазонов: http://momentjs.com/docs/#/plugins/range/
Такая же штука на питоне :)
Код
#!/usr/local/Cellar/python3/3.4.3/Frameworks/Python.framework/Versions/3.4/bin/python3.4
# -*- coding: utf-8 -*-
__author__ = 'admin'

api_url = "http://basicdata.ru/api/json/calend/"
from functools import lru_cache
import calendar
import datetime
import urllib.request
import json


def flatten(a):
    if isinstance(a, list):
        for b in a:
            for x in flatten(b):
                yield x
    else:
        yield a


# Генерирует расписание на заданный год по умолчанию
# Все дни с понедельника по пятницу отмечаются рабочими
# Все субботы и воскресенья - выходными
def generate_default_calendar(year):
    return group_by_month(
        map(lambda x: (x[0], x[1] < 6),
            map(lambda x: (x[0], x[1] + 1),
                filter(lambda x: x[0] > 0,
                       flatten(calendar.Calendar.yeardays2calendar(calendar.Calendar(), year))
                       )
                )
            )
    )


# Разделяет дни на группы по месяцам
def group_by_month_inner(items):
    month = []
    for day, flag in items:
        if month and month[-1][0] > day:
            # new month starting
            yield month
            month = []
        month.append((day, flag))
    if month:
        yield month


def group_by_month(items):
    return list(group_by_month_inner(items))


# Загружает дни-исключения
@lru_cache(maxsize=None)
def load_exceptions(apiurl, year):
    return json.loads(urllib.request.urlopen(apiurl).read().decode('utf8'))["data"][str(year)]


# Меняет значения структуры по умолчанию для дней-исключений

def apply_exceptions(months, exc):
    i = 0
    for m in months:
        newm = []
        i += 1
        for d in m:
            if str(i) in exc and str(d[0]) in exc[str(i)]:
                d = (d[0], exc[str(i)][str(d[0])]["isWorking"] != 2)
            newm.append(d)
        yield newm


# Удаляет все выходные, конвертирует кортежи в простые дни месяца
def filter_holidays(months):
    for m in months:
        yield list(
            map(
                lambda x: x[0],
                filter(
                    lambda x: x[1],
                    m
                )
            )
        )


# Получает все рабочие дни за определенные месяц/год в виде массива. Если указать месяц, вернет только его
def get_workdays(year=None, month=None):
    if year is None and month is None:
        year = datetime.datetime.now().year
        month = datetime.datetime.now().month
    if month is None:
        return list(filter_holidays(apply_exceptions(generate_default_calendar(year), load_exceptions(api_url, year))))
    else:
        return get_workdays(year)[month - 1]


# Считает кол-в рабочих дней в году/месяце
@lru_cache(maxsize=None)
def count_workdays(year=None, month=None):
    if month is None and year is None:
        return len(get_workdays())
    elif month is not None:
        return len(get_workdays(year, month))
    else:
        return sum(list(map(lambda x: len(x), get_workdays(year))), 0)

@lru_cache(maxsize=None)
def _get_expected_hours(year, month, day):
    return len(list(filter(lambda x: x < day, get_workdays()))) * 8
# Возвращает сколько часов ты уже должен был отработать
def get_expected_hours():
    return _get_expected_hours(datetime.datetime.now().year, datetime.datetime.now().month, datetime.datetime.now().day)


# Считает заработанные деньги исходя из зарплаты и кол-ва отработанны часов
@lru_cache(maxsize=None)
def earned(salary, hours):
    return hours / (count_workdays(datetime.datetime.now().year, datetime.datetime.now().month) * 8) * salary


def print_earned_with_stats(hours, salary=None):
    if salary is None:
        salary = 50000
    real = earned(salary, hours)
    expected = earned(salary, get_expected_hours())
    print("Earned: ", real, " Expected: ", expected)
    if real > expected:
        print("Well done, you've already earned extra ", real - expected, " money -", hours - get_expected_hours(),
              " extra hours worked")
    elif real < expected:
        print("You should work extra ", get_expected_hours() - hours, " hours to catch schedule")
    else:
        print("Going on schedule!")

if __name__ == '__main__':
    import sys
    _salary = None
    _hours = None
    if len(sys.argv) > 1:
        _hours = int(sys.argv[1])
    if len(sys.argv) > 2:
        _salary = int(sys.argv[2])
    if _hours is None:
        _hours = 8
    print_earned_with_stats(_hours, _salary)


А чего не на гитхабе? Вещь то годная.
Пока нету времени на такое, учебу с работой и то еле совмещаю)))
Спасибо. Стран слишком много в мире — надо будет подумать, как это сделать универсально.
Ну тут все просто — конечный результат вам от любого api нужен один — чтобы оно выдавало дни-исключения. Соответственно, можно из любого формата апи приводить к этому, универсальному, и работать с ним. Тогда использующему ваш модуль в очередной стране надо будет только найти апи для нее и реализовать функцию конвертации
Быть может заранее заготовить пакеты для стран, которые можно будет установить отдельно? Ну, допустим moment-weekday-calculator-public-holidays-ru и т.д. Как считаете?
ну в принципе имеет смысл, вряд ли большинство будет с ним работать более чем в одной стране.
Ок. Спасибо. Почешу репу, как время опять будет.
Я порекомендовал глянуть в сторону ics.
Парсится слёту, один вопрос — не забыть раз в год утянуть свежий список.

Мне нужен был датский, первый из интернетов —
www.officeholidays.com/ics/ics_country.php?tbl_country=Denmark
На самом деле, это серьёзный вопрос, кому доверять, и не стоит брать вот так вот первый попавшийся.
Кстати еще я не уверен, но вы похоже не учли вариант, что иногда праздничные дни делают рабочими(или если рабочий день в список исключений положить, он станет выходным?)
Тут всё конкретно — плагин принимает исключения для рабочих дней. Если выпадает на выходные, то просто ничего не меняет. На данный момент это остаётся на откуп разработчику — считать «mondaized» этот выходной или нет.
По-хорошему нужны два списка исключений: для рабочих и для выходных дней. Иногда рабочие дни переносят на выходной.
Сделаю, если кто-то запросит и найдётся на это время.
В некоторых странах еще и по отдельным провинциям/землям разные праздники. А есть и плавающие праздники, зависящие от даты пасхи.
Будьте осторожны при использовании этого API! Там неправильные данные, например basicdata.ru говорит, что 20 февраля 2015 — сокращенный на 1 час рабочий день, а на самом деле это не так.
Ну сокращенные на 1 час я не смотрел, а по кол-ву рабочих/нерабочих дней у них все правильно высчитывалось(сокращенные за рабочий считал)
Да, проблема может быть, если мы будем считать не количество рабочих дней, а количество рабочих часов. На всякий случай предупредил, сам чуть не наступил на эти грабли.
Sign up to leave a comment.

Articles