Плагин можно найти на github: https://github.com/andruhon/moment-weekday-calc
Плагин можно установить через bower и npm:
bower install moment-weekday-calc
npm install moment-weekday-calc
Плагин добавляет несколько функций в Moment.js:
- int weekdayCalc — считает сколько «рабочих» дней в заданном диапазоне
- date addWorkdays — находит дату спустя N «рабочих» (пн-пт) дней
- int workdaysToCalendarDays — конвертирует рабочие дни в календарные
- date addWeekdaysFromSet — добавляет дни из заданного множества к заданной дате
- int weekdaysFromSetToCalendarDays — конвертирует дни из заданного множетсва в календартные дни
Каждая из функций доступна с префиксом iso, такие функции используют множество рабочих дней начинающееся с понедельника (1-7), функции без префикса используют американский формат начинающийся с воскресенья (0-6).
Существует множество вариантов вызова этих функций, как с перечнем аргументов, так и с объектом с именованными параметрами, не буду их перечислять, а сразу перейду к примерам.
Использование:
Привожу примеры только для функций с префиксом iso.
Сколько пятниц между 14 и 23 февраля?
moment('14 Feb 2014').isoWeekdayCalc('23 Feb 2014',[5]); //2
(в данном случае начало диапазона берётся из объекта moment, из которого мы вызываем функцию)Сколько рабочих дней, без учёта праздников в с 1 апреля 2015 года по 31 марта 2016?
moment().isoWeekdayCalc('1 Apr 2015','31 Mar 2016',[1,2,3,4,5]); //262
(здесь объект moment не содержит даты, поэтому начальная дата задаётся в качестве первого аргумента)А если учесть пару праздников?
moment().isoWeekdayCalc('1 Apr 2015','31 Mar 2016',[1,2,3,4,5],['6 Apr 2015','7 Apr 2015']); //260
Вызов с объектом:
moment().isoWeekdayCalc({
rangeStart: '1 Apr 2015',
rangeEnd: '31 Mar 2016',
weekdays: [1,2,3,4,5],
exclusions: ['6 Apr 2015','7 Apr 2015']
}) //260
Что за дата будет спустя 5 рабочих дней после 2 февраля, если работать без выходных?
moment('2015-02-02').isoAddWeekdaysFromSet(5, [1,2,3,4,5,7]); //2015-02-08
5 рабочих дней после 4 мая, с учётом 9го мая?
moment('2015-05-04').isoAddWeekdaysFromSet({
'workdays': 5,
'weekdays': [1,2,3,4,5,6],
'exclusions': ['2015-05-09']
}); //2015-05-11
11 рабочих дней после 10 октября в календарных днях, рабочие дни — среда-воскресенье:
moment('2015-10-05').isoWeekdaysFromSetToCalendarDays(11, [3,4,5,6,7], ['2015-10-15']) //17
Подробнее в README на гитхабе.
Заранее спасибо за здравую критику. Надеюсь, что плагин будет кому-нибудь полезен.
Комментарии (21)
MaximChistov
21.09.2015 09:00+1Такая же штука на питоне :)
Код#!/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)
MaximChistov
21.09.2015 09:03Вот вам сразу реквест — хотите, чтобы ваш плагин использовали — грузите исключения сами :)
Советую basicdata.ru/api/json/calend Описание формата: basicdata.ru/api/calendAndruhon
21.09.2015 09:09Спасибо. Стран слишком много в мире — надо будет подумать, как это сделать универсально.
MaximChistov
21.09.2015 09:15Ну тут все просто — конечный результат вам от любого api нужен один — чтобы оно выдавало дни-исключения. Соответственно, можно из любого формата апи приводить к этому, универсальному, и работать с ним. Тогда использующему ваш модуль в очередной стране надо будет только найти апи для нее и реализовать функцию конвертации
Andruhon
21.09.2015 09:20Быть может заранее заготовить пакеты для стран, которые можно будет установить отдельно? Ну, допустим moment-weekday-calculator-public-holidays-ru и т.д. Как считаете?
MaximChistov
21.09.2015 09:21ну в принципе имеет смысл, вряд ли большинство будет с ним работать более чем в одной стране.
Andruhon
21.09.2015 09:24Ок. Спасибо. Почешу репу, как время опять будет.
WorksIsGone
21.09.2015 11:13+1Я порекомендовал глянуть в сторону ics.
Парсится слёту, один вопрос — не забыть раз в год утянуть свежий список.
Мне нужен был датский, первый из интернетов —
www.officeholidays.com/ics/ics_country.php?tbl_country=Denmark
На самом деле, это серьёзный вопрос, кому доверять, и не стоит брать вот так вот первый попавшийся.
MaximChistov
21.09.2015 09:18Кстати еще я не уверен, но вы похоже не учли вариант, что иногда праздничные дни делают рабочими(или если рабочий день в список исключений положить, он станет выходным?)
Andruhon
21.09.2015 09:23Тут всё конкретно — плагин принимает исключения для рабочих дней. Если выпадает на выходные, то просто ничего не меняет. На данный момент это остаётся на откуп разработчику — считать «mondaized» этот выходной или нет.
freakru
21.09.2015 12:13В некоторых странах еще и по отдельным провинциям/землям разные праздники. А есть и плавающие праздники, зависящие от даты пасхи.
sergeyZ
21.09.2015 13:25Будьте осторожны при использовании этого API! Там неправильные данные, например basicdata.ru говорит, что 20 февраля 2015 — сокращенный на 1 час рабочий день, а на самом деле это не так.
MaximChistov
21.09.2015 15:01Ну сокращенные на 1 час я не смотрел, а по кол-ву рабочих/нерабочих дней у них все правильно высчитывалось(сокращенные за рабочий считал)
sergeyZ
21.09.2015 15:07Да, проблема может быть, если мы будем считать не количество рабочих дней, а количество рабочих часов. На всякий случай предупредил, сам чуть не наступил на эти грабли.
vintage
Думаю тут не помешала бы интеграция с одним из плагинов для диапазонов: http://momentjs.com/docs/#/plugins/range/
Andruhon
Буду благодарен, если создадите запрос на githubе: github.com/andruhon/moment-weekday-calc/issues