clox 0.6__py3-none-any.whl → 0.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of clox might be problematic. Click here for more details.
- clox/functions.py +87 -17
- clox/jcalendar.py +359 -0
- clox/params.py +6 -3
- {clox-0.6.dist-info → clox-0.8.dist-info}/METADATA +51 -9
- clox-0.8.dist-info/RECORD +12 -0
- {clox-0.6.dist-info → clox-0.8.dist-info}/WHEEL +1 -1
- clox-0.6.dist-info/RECORD +0 -11
- {clox-0.6.dist-info → clox-0.8.dist-info}/AUTHORS.md +0 -0
- {clox-0.6.dist-info → clox-0.8.dist-info}/LICENSE +0 -0
- {clox-0.6.dist-info → clox-0.8.dist-info}/entry_points.txt +0 -0
- {clox-0.6.dist-info → clox-0.8.dist-info}/top_level.txt +0 -0
clox/functions.py
CHANGED
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
-
import
|
|
6
|
+
from calendar import TextCalendar as GregorianCalendar
|
|
7
7
|
import random
|
|
8
8
|
import datetime
|
|
9
|
+
import jdatetime
|
|
9
10
|
import argparse
|
|
10
11
|
import pytz
|
|
11
12
|
from art import tprint
|
|
13
|
+
from .jcalendar import TextCalendar as JalaliCalendar
|
|
12
14
|
from .params import HORIZONTAL_TIME_24H_FORMATS, VERTICAL_TIME_24H_FORMATS
|
|
13
15
|
from .params import HORIZONTAL_TIME_12H_FORMATS, VERTICAL_TIME_12H_FORMATS
|
|
14
|
-
from .params import
|
|
16
|
+
from .params import CLOX_VERSION, DATE_FORMAT
|
|
17
|
+
from .params import TIMEZONES_LIST, COUNTRIES_LIST
|
|
15
18
|
from .params import ADDITIONAL_INFO, EXIT_MESSAGE
|
|
16
|
-
from .params import FACES_MAP, FACES_LIST,
|
|
19
|
+
from .params import FACES_MAP, FACES_LIST, CALENDARS_LIST, DATE_SYSTEMS_LIST
|
|
17
20
|
from .params import HORIZONTAL_FACES_LIST_EXAMPLE, VERTICAL_FACES_LIST_EXAMPLE
|
|
18
21
|
from .params import CLOX_OVERVIEW, CLOX_REPO
|
|
19
22
|
|
|
@@ -75,18 +78,45 @@ def show_faces_list(vertical=False):
|
|
|
75
78
|
print('=' * 80)
|
|
76
79
|
|
|
77
80
|
|
|
78
|
-
def show_timezones_list():
|
|
81
|
+
def show_timezones_list(country=None):
|
|
79
82
|
"""
|
|
80
83
|
Show timezones list.
|
|
81
84
|
|
|
85
|
+
:param country: country iso3166 code
|
|
86
|
+
:type country: str
|
|
82
87
|
:return: None
|
|
83
88
|
"""
|
|
84
|
-
|
|
85
|
-
|
|
89
|
+
timezones_list = TIMEZONES_LIST
|
|
90
|
+
country_name = "All"
|
|
91
|
+
if country is not None:
|
|
92
|
+
timezones_list = list(map(lambda x: x.upper(), pytz.country_timezones(country)))
|
|
93
|
+
country_name = pytz.country_names[country]
|
|
94
|
+
try:
|
|
95
|
+
print("Timezones list ({country_name}):\n".format(country_name=country_name))
|
|
96
|
+
except Exception:
|
|
97
|
+
print("Timezones list ({country_name}):\n".format(country_name=country.upper()))
|
|
98
|
+
for index, timezone in enumerate(sorted(timezones_list), 1):
|
|
86
99
|
print("{index}. {timezone}".format(index=index, timezone=timezone))
|
|
87
100
|
|
|
88
101
|
|
|
89
|
-
def
|
|
102
|
+
def show_countries_list():
|
|
103
|
+
"""
|
|
104
|
+
Show countries list.
|
|
105
|
+
|
|
106
|
+
:return: None
|
|
107
|
+
"""
|
|
108
|
+
print("Countries list:\n")
|
|
109
|
+
for index, country_code in enumerate(sorted(COUNTRIES_LIST), 1):
|
|
110
|
+
country_name = pytz.country_names[country_code]
|
|
111
|
+
try:
|
|
112
|
+
print("{index}. {country_code} - {country_name}".format(index=index,
|
|
113
|
+
country_code=country_code, country_name=country_name))
|
|
114
|
+
except Exception:
|
|
115
|
+
print("{index}. {country_code} - {country_name}".format(index=index,
|
|
116
|
+
country_code=country_code, country_name=country_code))
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def print_calendar(mode="month", timezone=None, country=None, v_shift=0, h_shift=0, date_system="gregorian"):
|
|
90
120
|
"""
|
|
91
121
|
Print calendar.
|
|
92
122
|
|
|
@@ -94,34 +124,46 @@ def print_calendar(mode="month", timezone=None, v_shift=0, h_shift=0):
|
|
|
94
124
|
:type mode: str
|
|
95
125
|
:param timezone: timezone
|
|
96
126
|
:type timezone: str
|
|
127
|
+
:param country: country iso3166 code
|
|
128
|
+
:type country: str
|
|
97
129
|
:param v_shift: vertical shift
|
|
98
130
|
:type v_shift: int
|
|
99
131
|
:param h_shift: horizontal shift
|
|
100
132
|
:type h_shift: int
|
|
133
|
+
:param date_system: date system
|
|
134
|
+
:type date_system: str
|
|
101
135
|
:return: None
|
|
102
136
|
"""
|
|
137
|
+
datetime_lib = datetime
|
|
138
|
+
calendar_obj = GregorianCalendar()
|
|
139
|
+
if date_system == "jalali":
|
|
140
|
+
datetime_lib = jdatetime
|
|
141
|
+
calendar_obj = JalaliCalendar()
|
|
103
142
|
tz = None
|
|
104
143
|
timezone_str = "Local"
|
|
144
|
+
if country is not None:
|
|
145
|
+
timezone = pytz.country_timezones(country)[0].upper()
|
|
105
146
|
if timezone is not None:
|
|
106
147
|
timezone_str = timezone
|
|
107
148
|
tz = pytz.timezone(timezone)
|
|
108
149
|
v_shift = max(0, v_shift)
|
|
109
150
|
h_shift = max(0, h_shift)
|
|
110
|
-
datetime_now =
|
|
151
|
+
datetime_now = datetime_lib.datetime.now(tz=tz)
|
|
111
152
|
current_date = datetime_now.strftime(DATE_FORMAT)
|
|
112
153
|
print('\n' * v_shift, end='')
|
|
113
154
|
print(" " * h_shift, end='')
|
|
114
155
|
print("Today: {date}".format(date=current_date))
|
|
115
156
|
print(" " * h_shift, end='')
|
|
116
157
|
print("Timezone: {timezone}\n".format(timezone=timezone_str))
|
|
117
|
-
calendar_str =
|
|
158
|
+
calendar_str = calendar_obj.formatmonth(datetime_now.year, datetime_now.month)
|
|
118
159
|
if mode == "year":
|
|
119
|
-
calendar_str =
|
|
160
|
+
calendar_str = calendar_obj.formatyear(datetime_now.year)
|
|
120
161
|
print("\n".join([" " * h_shift + x for x in calendar_str.split("\n")]))
|
|
121
162
|
|
|
122
163
|
|
|
123
164
|
def run_clock(
|
|
124
165
|
timezone=None,
|
|
166
|
+
country=None,
|
|
125
167
|
v_shift=0,
|
|
126
168
|
h_shift=0,
|
|
127
169
|
face=1,
|
|
@@ -129,12 +171,15 @@ def run_clock(
|
|
|
129
171
|
vertical=False,
|
|
130
172
|
hide_date=False,
|
|
131
173
|
hide_timezone=False,
|
|
132
|
-
am_pm=False
|
|
174
|
+
am_pm=False,
|
|
175
|
+
date_system="gregorian"):
|
|
133
176
|
"""
|
|
134
177
|
Run clock.
|
|
135
178
|
|
|
136
179
|
:param timezone: timezone
|
|
137
180
|
:type timezone: str
|
|
181
|
+
:param country: country iso3166 code
|
|
182
|
+
:type country: str
|
|
138
183
|
:param v_shift: vertical shift
|
|
139
184
|
:type v_shift: int
|
|
140
185
|
:param h_shift: horizontal shift
|
|
@@ -151,14 +196,21 @@ def run_clock(
|
|
|
151
196
|
:type hide_timezone: bool
|
|
152
197
|
:param am_pm: AM/PM mode flag
|
|
153
198
|
:type am_pm: bool
|
|
199
|
+
:param date_system: date system
|
|
200
|
+
:type date_system: str
|
|
154
201
|
:return: None
|
|
155
202
|
"""
|
|
203
|
+
datetime_lib = datetime
|
|
204
|
+
if date_system == "jalali":
|
|
205
|
+
datetime_lib = jdatetime
|
|
156
206
|
format_index = 0
|
|
157
207
|
time_formats = HORIZONTAL_TIME_12H_FORMATS if am_pm else HORIZONTAL_TIME_24H_FORMATS
|
|
158
208
|
if vertical:
|
|
159
209
|
time_formats = VERTICAL_TIME_12H_FORMATS if am_pm else VERTICAL_TIME_24H_FORMATS
|
|
160
210
|
tz = None
|
|
161
211
|
timezone_str = "Local"
|
|
212
|
+
if country is not None:
|
|
213
|
+
timezone = pytz.country_timezones(country)[0].upper()
|
|
162
214
|
if timezone is not None:
|
|
163
215
|
timezone_str = timezone
|
|
164
216
|
tz = pytz.timezone(timezone)
|
|
@@ -169,7 +221,7 @@ def run_clock(
|
|
|
169
221
|
clear_screen()
|
|
170
222
|
print('\n' * v_shift, end='')
|
|
171
223
|
print(" " * h_shift, end='')
|
|
172
|
-
datetime_now =
|
|
224
|
+
datetime_now = datetime_lib.datetime.now(tz=tz)
|
|
173
225
|
current_time = datetime_now.strftime(time_formats[format_index])
|
|
174
226
|
current_date = datetime_now.strftime(DATE_FORMAT)
|
|
175
227
|
tprint(current_time, font=face, sep="\n" + " " * h_shift)
|
|
@@ -192,7 +244,8 @@ def main():
|
|
|
192
244
|
"""
|
|
193
245
|
parser = argparse.ArgumentParser()
|
|
194
246
|
parser.epilog = ADDITIONAL_INFO
|
|
195
|
-
parser.add_argument('--timezone', help='timezone', type=str, choices=TIMEZONES_LIST)
|
|
247
|
+
parser.add_argument('--timezone', help='timezone', type=str.upper, choices=TIMEZONES_LIST)
|
|
248
|
+
parser.add_argument('--country', help='country (iso3166 code)', type=str.upper, choices=COUNTRIES_LIST)
|
|
196
249
|
parser.add_argument('--v-shift', help='vertical shift', type=int, default=0)
|
|
197
250
|
parser.add_argument('--h-shift', help='horizontal shift', type=int, default=0)
|
|
198
251
|
parser.add_argument('--version', help='version', nargs="?", const=1)
|
|
@@ -200,12 +253,19 @@ def main():
|
|
|
200
253
|
parser.add_argument('--face', help='face', type=int, choices=FACES_LIST, default=1)
|
|
201
254
|
parser.add_argument('--faces-list', help='faces list', nargs="?", const=1)
|
|
202
255
|
parser.add_argument('--timezones-list', help='timezones list', nargs="?", const=1)
|
|
256
|
+
parser.add_argument('--countries-list', help='countries list', nargs="?", const=1)
|
|
203
257
|
parser.add_argument('--no-blink', help='disable blinking mode', nargs="?", const=1)
|
|
204
258
|
parser.add_argument('--vertical', help='vertical mode', nargs="?", const=1)
|
|
205
259
|
parser.add_argument('--hide-date', help='hide date', nargs="?", const=1)
|
|
206
260
|
parser.add_argument('--hide-timezone', help='hide timezone', nargs="?", const=1)
|
|
207
261
|
parser.add_argument('--am-pm', help='AM/PM mode', nargs="?", const=1)
|
|
208
|
-
parser.add_argument('--calendar', help='calendar mode', type=str, choices=
|
|
262
|
+
parser.add_argument('--calendar', help='calendar mode', type=str.lower, choices=CALENDARS_LIST)
|
|
263
|
+
parser.add_argument(
|
|
264
|
+
'--date-system',
|
|
265
|
+
help='date system',
|
|
266
|
+
type=str.lower,
|
|
267
|
+
choices=DATE_SYSTEMS_LIST,
|
|
268
|
+
default="gregorian")
|
|
209
269
|
args = parser.parse_args()
|
|
210
270
|
if args.version:
|
|
211
271
|
print(CLOX_VERSION)
|
|
@@ -214,13 +274,22 @@ def main():
|
|
|
214
274
|
elif args.faces_list:
|
|
215
275
|
show_faces_list(vertical=args.vertical)
|
|
216
276
|
elif args.timezones_list:
|
|
217
|
-
show_timezones_list()
|
|
277
|
+
show_timezones_list(args.country)
|
|
278
|
+
elif args.countries_list:
|
|
279
|
+
show_countries_list()
|
|
218
280
|
elif args.calendar:
|
|
219
|
-
print_calendar(
|
|
281
|
+
print_calendar(
|
|
282
|
+
mode=args.calendar,
|
|
283
|
+
timezone=args.timezone,
|
|
284
|
+
country=args.country,
|
|
285
|
+
h_shift=args.h_shift,
|
|
286
|
+
v_shift=args.v_shift,
|
|
287
|
+
date_system=args.date_system)
|
|
220
288
|
else:
|
|
221
289
|
try:
|
|
222
290
|
run_clock(
|
|
223
291
|
timezone=args.timezone,
|
|
292
|
+
country=args.country,
|
|
224
293
|
h_shift=args.h_shift,
|
|
225
294
|
v_shift=args.v_shift,
|
|
226
295
|
face=args.face,
|
|
@@ -228,6 +297,7 @@ def main():
|
|
|
228
297
|
vertical=args.vertical,
|
|
229
298
|
hide_date=args.hide_date,
|
|
230
299
|
hide_timezone=args.hide_timezone,
|
|
231
|
-
am_pm=args.am_pm
|
|
300
|
+
am_pm=args.am_pm,
|
|
301
|
+
date_system=args.date_system)
|
|
232
302
|
except (KeyboardInterrupt, EOFError):
|
|
233
303
|
print(EXIT_MESSAGE)
|
clox/jcalendar.py
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""clox jalali calendar."""
|
|
3
|
+
# Reference: https://github.com/IKermani/jcalendar
|
|
4
|
+
import datetime
|
|
5
|
+
from itertools import repeat
|
|
6
|
+
import jdatetime
|
|
7
|
+
|
|
8
|
+
# Exception raised for bad input (with string parameter for details)
|
|
9
|
+
error = ValueError
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
# Exceptions raised for bad input
|
|
13
|
+
class IllegalMonthError(ValueError):
|
|
14
|
+
"""Illegal month error."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, month):
|
|
17
|
+
"""Initiate."""
|
|
18
|
+
self.month = month
|
|
19
|
+
|
|
20
|
+
def __str__(self):
|
|
21
|
+
"""Return string representation."""
|
|
22
|
+
return "bad month number %r; must be 1-12" % self.month
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class IllegalWeekdayError(ValueError):
|
|
26
|
+
"""Illegal weekday error."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, weekday):
|
|
29
|
+
"""Initiate."""
|
|
30
|
+
self.weekday = weekday
|
|
31
|
+
|
|
32
|
+
def __str__(self):
|
|
33
|
+
"""Return string representation."""
|
|
34
|
+
return "bad weekday number %r; must be 0 (Shanbe) to 6 (Jom'e)" % self.weekday
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# Constants for months referenced later
|
|
38
|
+
January = 1
|
|
39
|
+
February = 2
|
|
40
|
+
|
|
41
|
+
Farvardin = 1
|
|
42
|
+
Esfand = 12
|
|
43
|
+
|
|
44
|
+
# Number of days per month (except for Esfand in leap years)
|
|
45
|
+
mdays = [0, 31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29]
|
|
46
|
+
|
|
47
|
+
# Full and abbreviated names of weekdays
|
|
48
|
+
day_name = jdatetime.date.j_weekdays_en
|
|
49
|
+
day_abbr = jdatetime.date.j_weekdays_short_en
|
|
50
|
+
# day_abbr = ['Sh', 'Ye', 'Do', 'Se', 'Ch', 'Pa', 'Jo']
|
|
51
|
+
|
|
52
|
+
# Full and abbreviated names of months (1-based arrays!!!)
|
|
53
|
+
month_name = [0] + jdatetime.date.j_months_en
|
|
54
|
+
month_abbr = [0] + jdatetime.date.j_months_short_en
|
|
55
|
+
|
|
56
|
+
# Constants for weekdays
|
|
57
|
+
(MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) = range(7)
|
|
58
|
+
(DOSHANBE, SESHANBE, CHAHARSHANBE, PANJSHANBE, JOME, SHANBE, YEKSHANBE) = range(7)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def isleap(year):
|
|
62
|
+
"""Return True for leap years, False for non-leap years."""
|
|
63
|
+
return jdatetime.date(year, 1, 1).isleap()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def leapdays(y1, y2):
|
|
67
|
+
"""Return number of leap years in range [y1, y2)."""
|
|
68
|
+
leapdays = 0
|
|
69
|
+
|
|
70
|
+
for year in range(y1, y2):
|
|
71
|
+
if isleap(year):
|
|
72
|
+
leapdays += 1
|
|
73
|
+
|
|
74
|
+
return leapdays
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def weekday(year, month, day):
|
|
78
|
+
"""Return week-day (0-6 ~ Mon-Sun)."""
|
|
79
|
+
if not datetime.MINYEAR <= year <= datetime.MAXYEAR:
|
|
80
|
+
year = 2000 + year % 400
|
|
81
|
+
return jdatetime.date(year, month, day).weekday()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def monthrange(year, month):
|
|
85
|
+
"""Return weekday (0-6 ~ Mon-Sun) and number of days (28-31) for year, month."""
|
|
86
|
+
if not 1 <= month <= 12:
|
|
87
|
+
raise IllegalMonthError(month)
|
|
88
|
+
day1 = weekday(year, month, 1)
|
|
89
|
+
ndays = mdays[month] + (month == Esfand and isleap(year))
|
|
90
|
+
return day1, ndays
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _monthlen(year, month):
|
|
94
|
+
"""Return length of month."""
|
|
95
|
+
return mdays[month] + (month == Esfand and isleap(year))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _prevmonth(year, month):
|
|
99
|
+
"""Return previous month."""
|
|
100
|
+
if month == 1:
|
|
101
|
+
return year - 1, 12
|
|
102
|
+
else:
|
|
103
|
+
return year, month - 1
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _nextmonth(year, month):
|
|
107
|
+
"""Return next month."""
|
|
108
|
+
if month == 12:
|
|
109
|
+
return year + 1, 1
|
|
110
|
+
else:
|
|
111
|
+
return year, month + 1
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Calendar(object):
|
|
115
|
+
"""Base calendar class. This class doesn't do any formatting. It simply provides data to subclasses."""
|
|
116
|
+
|
|
117
|
+
def __init__(self, firstweekday=0):
|
|
118
|
+
"""Initiate."""
|
|
119
|
+
self.firstweekday = firstweekday # 0 = Doshanbe, 6 = Yekshanbe
|
|
120
|
+
|
|
121
|
+
def getfirstweekday(self):
|
|
122
|
+
"""Get first weekday."""
|
|
123
|
+
return self._firstweekday % 7
|
|
124
|
+
|
|
125
|
+
def setfirstweekday(self, firstweekday):
|
|
126
|
+
"""Set first weekday."""
|
|
127
|
+
self._firstweekday = firstweekday
|
|
128
|
+
|
|
129
|
+
firstweekday = property(getfirstweekday, setfirstweekday)
|
|
130
|
+
|
|
131
|
+
def iterweekdays(self):
|
|
132
|
+
"""Return an iterator for one week of weekday numbers starting with the configured first one."""
|
|
133
|
+
for i in range(self.firstweekday, self.firstweekday + 7):
|
|
134
|
+
yield i % 7
|
|
135
|
+
|
|
136
|
+
def itermonthdates(self, year, month):
|
|
137
|
+
"""Return an iterator for one month."""
|
|
138
|
+
for y, m, d in self.itermonthdays3(year, month):
|
|
139
|
+
yield jdatetime.date(y, m, d)
|
|
140
|
+
|
|
141
|
+
def itermonthdays(self, year, month):
|
|
142
|
+
"""Like itermonthdates(), but will yield day numbers. For days outside the specified month the day number is 0."""
|
|
143
|
+
day1, ndays = monthrange(year, month)
|
|
144
|
+
days_before = (day1 - self.firstweekday) % 7
|
|
145
|
+
yield from repeat(0, days_before)
|
|
146
|
+
yield from range(1, ndays + 1)
|
|
147
|
+
days_after = (self.firstweekday - day1 - ndays) % 7
|
|
148
|
+
yield from repeat(0, days_after)
|
|
149
|
+
|
|
150
|
+
def itermonthdays2(self, year, month):
|
|
151
|
+
"""Like itermonthdates(), but will yield (day number, weekday number) tuples. For days outside the specified month the day number is 0."""
|
|
152
|
+
for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday):
|
|
153
|
+
yield d, i % 7
|
|
154
|
+
|
|
155
|
+
def itermonthdays3(self, year, month):
|
|
156
|
+
"""Like itermonthdates(), but will yield (year, month, day) tuples. Can be used for dates outside of datetime.date range."""
|
|
157
|
+
day1, ndays = monthrange(year, month)
|
|
158
|
+
days_before = (day1 - self.firstweekday) % 7
|
|
159
|
+
days_after = (self.firstweekday - day1 - ndays) % 7
|
|
160
|
+
y, m = _prevmonth(year, month)
|
|
161
|
+
end = _monthlen(y, m) + 1
|
|
162
|
+
for d in range(end - days_before, end):
|
|
163
|
+
yield y, m, d
|
|
164
|
+
for d in range(1, ndays + 1):
|
|
165
|
+
yield year, month, d
|
|
166
|
+
y, m = _nextmonth(year, month)
|
|
167
|
+
for d in range(1, days_after + 1):
|
|
168
|
+
yield y, m, d
|
|
169
|
+
|
|
170
|
+
def itermonthdays4(self, year, month):
|
|
171
|
+
"""Like itermonthdates(), but will yield (year, month, day, day_of_week) tuples. Can be used for dates outside of datetime.date range."""
|
|
172
|
+
for i, (y, m, d) in enumerate(self.itermonthdays3(year, month)):
|
|
173
|
+
yield y, m, d, (self.firstweekday + i) % 7
|
|
174
|
+
|
|
175
|
+
def monthdatescalendar(self, year, month):
|
|
176
|
+
"""Return a matrix (list of lists) representing a month's calendar.Each row represents a week; week entries are datetime.date values."""
|
|
177
|
+
dates = list(self.itermonthdates(year, month))
|
|
178
|
+
return [dates[i:i + 7] for i in range(0, len(dates), 7)]
|
|
179
|
+
|
|
180
|
+
def monthdays2calendar(self, year, month):
|
|
181
|
+
"""Return a matrix representing a month's calendar."""
|
|
182
|
+
days = list(self.itermonthdays2(year, month))
|
|
183
|
+
return [days[i:i + 7] for i in range(0, len(days), 7)]
|
|
184
|
+
|
|
185
|
+
def monthdayscalendar(self, year, month):
|
|
186
|
+
"""Return a matrix representing a month's calendar."""
|
|
187
|
+
days = list(self.itermonthdays(year, month))
|
|
188
|
+
return [days[i:i + 7] for i in range(0, len(days), 7)]
|
|
189
|
+
|
|
190
|
+
def yeardatescalendar(self, year, width=3):
|
|
191
|
+
"""Return the data for the specified year ready for formatting."""
|
|
192
|
+
months = [
|
|
193
|
+
self.monthdatescalendar(year, i)
|
|
194
|
+
for i in range(Farvardin, Farvardin + 12)
|
|
195
|
+
]
|
|
196
|
+
return [months[i:i + width] for i in range(0, len(months), width)]
|
|
197
|
+
|
|
198
|
+
def yeardays2calendar(self, year, width=3):
|
|
199
|
+
"""Return the data for the specified year ready for formatting."""
|
|
200
|
+
months = [
|
|
201
|
+
self.monthdays2calendar(year, i)
|
|
202
|
+
for i in range(Farvardin, Farvardin + 12)
|
|
203
|
+
]
|
|
204
|
+
return [months[i:i + width] for i in range(0, len(months), width)]
|
|
205
|
+
|
|
206
|
+
def yeardayscalendar(self, year, width=3):
|
|
207
|
+
"""Return the data for the specified year ready for formatting."""
|
|
208
|
+
months = [
|
|
209
|
+
self.monthdayscalendar(year, i)
|
|
210
|
+
for i in range(Farvardin, Farvardin + 12)
|
|
211
|
+
]
|
|
212
|
+
return [months[i:i + width] for i in range(0, len(months), width)]
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class TextCalendar(Calendar):
|
|
216
|
+
"""Subclass of Calendar that outputs a calendar as a simple plain text similar to the UNIX program cal."""
|
|
217
|
+
|
|
218
|
+
def prweek(self, theweek, width):
|
|
219
|
+
"""Print a single week (no newline)."""
|
|
220
|
+
print(self.formatweek(theweek, width), end='')
|
|
221
|
+
|
|
222
|
+
def formatday(self, day, weekday, width):
|
|
223
|
+
"""Return a formatted day."""
|
|
224
|
+
if day == 0:
|
|
225
|
+
s = ''
|
|
226
|
+
else:
|
|
227
|
+
s = '%2i' % day # right-align single-digit days
|
|
228
|
+
return s.center(width)
|
|
229
|
+
|
|
230
|
+
def formatweek(self, theweek, width):
|
|
231
|
+
"""Return a single week in a string (no newline)."""
|
|
232
|
+
return ' '.join(self.formatday(d, wd, width) for (d, wd) in theweek)
|
|
233
|
+
|
|
234
|
+
def formatweekday(self, day, width):
|
|
235
|
+
"""Return a formatted week day name."""
|
|
236
|
+
if width >= 9:
|
|
237
|
+
names = day_name
|
|
238
|
+
else:
|
|
239
|
+
names = day_abbr
|
|
240
|
+
return names[day][:width].center(width)
|
|
241
|
+
|
|
242
|
+
def formatweekheader(self, width):
|
|
243
|
+
"""Return a header for a week."""
|
|
244
|
+
return ' '.join(self.formatweekday(i, width) for i in self.iterweekdays())
|
|
245
|
+
|
|
246
|
+
def formatmonthname(self, theyear, themonth, width, withyear=True):
|
|
247
|
+
"""Return a formatted month name."""
|
|
248
|
+
s = month_name[themonth]
|
|
249
|
+
if withyear:
|
|
250
|
+
s = "%s %r" % (s, theyear)
|
|
251
|
+
return s.center(width)
|
|
252
|
+
|
|
253
|
+
def prmonth(self, theyear, themonth, w=0, l=0):
|
|
254
|
+
"""Print a month's calendar."""
|
|
255
|
+
print(self.formatmonth(theyear, themonth, w, l), end='')
|
|
256
|
+
|
|
257
|
+
def formatmonth(self, theyear, themonth, w=0, l=0):
|
|
258
|
+
"""Return a month's calendar string (multi-line)."""
|
|
259
|
+
w = max(2, w)
|
|
260
|
+
l = max(1, l)
|
|
261
|
+
s = self.formatmonthname(theyear, themonth, 7 * (w + 1) - 1)
|
|
262
|
+
s = s.rstrip()
|
|
263
|
+
s += '\n' * l
|
|
264
|
+
s += self.formatweekheader(w).rstrip()
|
|
265
|
+
s += '\n' * l
|
|
266
|
+
for week in self.monthdays2calendar(theyear, themonth):
|
|
267
|
+
s += self.formatweek(week, w).rstrip()
|
|
268
|
+
s += '\n' * l
|
|
269
|
+
return s
|
|
270
|
+
|
|
271
|
+
def formatyear(self, theyear, w=2, l=1, c=6, m=3):
|
|
272
|
+
"""Return a year's calendar as a multi-line string."""
|
|
273
|
+
w = max(2, w)
|
|
274
|
+
l = max(1, l)
|
|
275
|
+
c = max(2, c)
|
|
276
|
+
colwidth = (w + 1) * 7 - 1
|
|
277
|
+
v = []
|
|
278
|
+
a = v.append
|
|
279
|
+
a(repr(theyear).center(colwidth * m + c * (m - 1)).rstrip())
|
|
280
|
+
a('\n' * l)
|
|
281
|
+
header = self.formatweekheader(w)
|
|
282
|
+
for (i, row) in enumerate(self.yeardays2calendar(theyear, m)):
|
|
283
|
+
# months in this row
|
|
284
|
+
months = range(m * i + 1, min(m * (i + 1) + 1, 13))
|
|
285
|
+
a('\n' * l)
|
|
286
|
+
names = (self.formatmonthname(theyear, k, colwidth, False)
|
|
287
|
+
for k in months)
|
|
288
|
+
a(formatstring(names, colwidth, c).rstrip())
|
|
289
|
+
a('\n' * l)
|
|
290
|
+
headers = (header for k in months)
|
|
291
|
+
a(formatstring(headers, colwidth, c).rstrip())
|
|
292
|
+
a('\n' * l)
|
|
293
|
+
# max number of weeks for this row
|
|
294
|
+
height = max(len(cal) for cal in row)
|
|
295
|
+
for j in range(height):
|
|
296
|
+
weeks = []
|
|
297
|
+
for cal in row:
|
|
298
|
+
if j >= len(cal):
|
|
299
|
+
weeks.append('')
|
|
300
|
+
else:
|
|
301
|
+
weeks.append(self.formatweek(cal[j], w))
|
|
302
|
+
a(formatstring(weeks, colwidth, c).rstrip())
|
|
303
|
+
a('\n' * l)
|
|
304
|
+
return ''.join(v)
|
|
305
|
+
|
|
306
|
+
def pryear(self, theyear, w=0, l=0, c=6, m=3):
|
|
307
|
+
"""Print a year's calendar."""
|
|
308
|
+
print(self.formatyear(theyear, w, l, c, m), end='')
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
c = TextCalendar()
|
|
312
|
+
|
|
313
|
+
firstweekday = c.getfirstweekday
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def setfirstweekday(firstweekday):
|
|
317
|
+
"""Set first weekday."""
|
|
318
|
+
if not DOSHANBE <= firstweekday <= YEKSHANBE:
|
|
319
|
+
raise IllegalWeekdayError(firstweekday)
|
|
320
|
+
c.firstweekday = firstweekday
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
monthcalendar = c.monthdayscalendar
|
|
324
|
+
prweek = c.prweek
|
|
325
|
+
week = c.formatweek
|
|
326
|
+
weekheader = c.formatweekheader
|
|
327
|
+
prmonth = c.prmonth
|
|
328
|
+
month = c.formatmonth
|
|
329
|
+
calendar = c.formatyear
|
|
330
|
+
prcal = c.pryear
|
|
331
|
+
|
|
332
|
+
# Spacing of month columns for multi-column year calendar
|
|
333
|
+
_colwidth = 7 * 3 - 1 # Amount printed by prweek()
|
|
334
|
+
_spacing = 6 # Number of spaces between columns
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def format(cols, colwidth=_colwidth, spacing=_spacing):
|
|
338
|
+
"""Print multi-column formatting for year calendars."""
|
|
339
|
+
print(formatstring(cols, colwidth, spacing))
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def formatstring(cols, colwidth=_colwidth, spacing=_spacing):
|
|
343
|
+
"""Return a string formatted from n strings, centered within n columns."""
|
|
344
|
+
spacing *= ' '
|
|
345
|
+
return spacing.join(c.center(colwidth) for c in cols)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
EPOCH = 1970
|
|
349
|
+
_EPOCH_ORD = jdatetime.date(EPOCH, 1, 1).toordinal()
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def timegm(tuple):
|
|
353
|
+
"""Unrelated but handy function to calculate Unix timestamp from GMT."""
|
|
354
|
+
year, month, day, hour, minute, second = tuple[:6]
|
|
355
|
+
days = jdatetime.date(year, month, 1).toordinal() - _EPOCH_ORD + day - 1
|
|
356
|
+
hours = days * 24 + hour
|
|
357
|
+
minutes = hours * 60 + minute
|
|
358
|
+
seconds = minutes * 60 + second
|
|
359
|
+
return seconds
|
clox/params.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"""clox params."""
|
|
3
3
|
import pytz
|
|
4
4
|
|
|
5
|
-
CLOX_VERSION = "0.
|
|
5
|
+
CLOX_VERSION = "0.8"
|
|
6
6
|
|
|
7
7
|
CLOX_OVERVIEW = '''
|
|
8
8
|
Clox is a terminal-based clock application designed for terminal enthusiasts who appreciate simplicity,
|
|
@@ -24,7 +24,8 @@ HORIZONTAL_TIME_12H_FORMATS = ['%I:%M %p', '%I:%M %p.']
|
|
|
24
24
|
VERTICAL_TIME_12H_FORMATS = ['%I\n%M\n%p', '%I\n%M\n%p.']
|
|
25
25
|
DATE_FORMAT = "%A, %B %d, %Y"
|
|
26
26
|
|
|
27
|
-
TIMEZONES_LIST = pytz.all_timezones
|
|
27
|
+
TIMEZONES_LIST = list(map(lambda x: x.upper(), pytz.all_timezones))
|
|
28
|
+
COUNTRIES_LIST = list(map(lambda x: x.upper(), pytz.country_timezones.keys()))
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
FACES_MAP = {
|
|
@@ -57,4 +58,6 @@ FACES_MAP = {
|
|
|
57
58
|
|
|
58
59
|
FACES_LIST = [-1] + sorted(FACES_MAP)
|
|
59
60
|
|
|
60
|
-
|
|
61
|
+
CALENDARS_LIST = ["month", "year"]
|
|
62
|
+
|
|
63
|
+
DATE_SYSTEMS_LIST = ["gregorian", "jalali"]
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: clox
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8
|
|
4
4
|
Summary: A Geeky Clock for Terminal Enthusiasts
|
|
5
5
|
Home-page: https://github.com/sepandhaghighi/clox
|
|
6
|
-
Download-URL: https://github.com/sepandhaghighi/clox/tarball/v0.
|
|
6
|
+
Download-URL: https://github.com/sepandhaghighi/clox/tarball/v0.8
|
|
7
7
|
Author: Sepand Haghighi
|
|
8
8
|
Author-email: me@sepand.tech
|
|
9
9
|
License: MIT
|
|
@@ -32,6 +32,7 @@ Description-Content-Type: text/markdown
|
|
|
32
32
|
License-File: LICENSE
|
|
33
33
|
License-File: AUTHORS.md
|
|
34
34
|
Requires-Dist: art>=5.3
|
|
35
|
+
Requires-Dist: jdatetime>=3.8.2
|
|
35
36
|
Requires-Dist: pytz>=2019.2
|
|
36
37
|
Dynamic: author
|
|
37
38
|
Dynamic: author-email
|
|
@@ -103,13 +104,13 @@ Clox is a terminal-based clock application designed for terminal enthusiasts who
|
|
|
103
104
|
## Installation
|
|
104
105
|
|
|
105
106
|
### Source Code
|
|
106
|
-
- Download [Version 0.
|
|
107
|
+
- Download [Version 0.8](https://github.com/sepandhaghighi/clox/archive/v0.8.zip) or [Latest Source](https://github.com/sepandhaghighi/clox/archive/dev.zip)
|
|
107
108
|
- `pip install .`
|
|
108
109
|
|
|
109
110
|
### PyPI
|
|
110
111
|
|
|
111
112
|
- Check [Python Packaging User Guide](https://packaging.python.org/installing/)
|
|
112
|
-
- `pip install clox==0.
|
|
113
|
+
- `pip install clox==0.8`
|
|
113
114
|
|
|
114
115
|
|
|
115
116
|
## Usage
|
|
@@ -142,16 +143,30 @@ clox
|
|
|
142
143
|
clox --face=3
|
|
143
144
|
```
|
|
144
145
|
* Use `--face=-1` for random mode
|
|
145
|
-
* [Faces List](https://github.com/sepandhaghighi/clox/blob/main/FACES.md)
|
|
146
|
-
|
|
146
|
+
* [Faces List](https://github.com/sepandhaghighi/clox/blob/main/FACES.md): `clox --faces-list`
|
|
147
|
+
|
|
147
148
|
|
|
148
149
|
### Timezone
|
|
149
150
|
|
|
150
151
|
```console
|
|
151
152
|
clox --timezone="Etc/GMT+7"
|
|
152
153
|
```
|
|
153
|
-
* [Timezones List](https://github.com/sepandhaghighi/clox/blob/main/TIMEZONES.md)
|
|
154
|
-
|
|
154
|
+
* [Timezones List](https://github.com/sepandhaghighi/clox/blob/main/TIMEZONES.md): `clox --timezones-list`
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
### Country
|
|
158
|
+
|
|
159
|
+
The `--country` argument allows you to specify a country using its **ISO 3166** code format
|
|
160
|
+
|
|
161
|
+
ℹ️ When the `--country` argument is provided, the `--timezone` argument will be ignored
|
|
162
|
+
|
|
163
|
+
ℹ️ If the specified country has multiple timezones, the first timezone will be selected automatically
|
|
164
|
+
|
|
165
|
+
```console
|
|
166
|
+
clox --country="DE"
|
|
167
|
+
```
|
|
168
|
+
* [Countries List](https://github.com/sepandhaghighi/clox/blob/main/COUNTRIES.md): `clox --countries-list`
|
|
169
|
+
|
|
155
170
|
|
|
156
171
|
### Vertical/Horizontal Shift
|
|
157
172
|
|
|
@@ -209,6 +224,16 @@ In this mode, the calendar will be displayed
|
|
|
209
224
|
clox --calendar=month
|
|
210
225
|
```
|
|
211
226
|
|
|
227
|
+
### Date System
|
|
228
|
+
|
|
229
|
+
ℹ️ Valid choices: [`gregorian`, `jalali`]
|
|
230
|
+
|
|
231
|
+
ℹ️ The default date system is `gregorian`
|
|
232
|
+
|
|
233
|
+
```console
|
|
234
|
+
clox --date-system=jalali
|
|
235
|
+
```
|
|
236
|
+
|
|
212
237
|
## Screen Record
|
|
213
238
|
|
|
214
239
|
<div align="center">
|
|
@@ -267,6 +292,21 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
|
|
|
267
292
|
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
|
|
268
293
|
|
|
269
294
|
## [Unreleased]
|
|
295
|
+
## [0.8] - 2025-03-16
|
|
296
|
+
### Added
|
|
297
|
+
- `--country` argument
|
|
298
|
+
- `--countries-list` argument
|
|
299
|
+
- `COUNTRIES.md`
|
|
300
|
+
### Changed
|
|
301
|
+
- Input case sensitivity bug fixed
|
|
302
|
+
- Test system modified
|
|
303
|
+
- `README.md` updated
|
|
304
|
+
## [0.7] - 2025-03-06
|
|
305
|
+
### Added
|
|
306
|
+
- Jalali calendar
|
|
307
|
+
- `--date-system` argument
|
|
308
|
+
### Changed
|
|
309
|
+
- `README.md` updated
|
|
270
310
|
## [0.6] - 2025-02-25
|
|
271
311
|
### Added
|
|
272
312
|
- `--calendar` argument
|
|
@@ -305,7 +345,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
|
305
345
|
- `TIMEZONES.md`
|
|
306
346
|
- `FACES.md`
|
|
307
347
|
|
|
308
|
-
[Unreleased]: https://github.com/sepandhaghighi/clox/compare/v0.
|
|
348
|
+
[Unreleased]: https://github.com/sepandhaghighi/clox/compare/v0.8...dev
|
|
349
|
+
[0.8]: https://github.com/sepandhaghighi/clox/compare/v0.7...v0.8
|
|
350
|
+
[0.7]: https://github.com/sepandhaghighi/clox/compare/v0.6...v0.7
|
|
309
351
|
[0.6]: https://github.com/sepandhaghighi/clox/compare/v0.5...v0.6
|
|
310
352
|
[0.5]: https://github.com/sepandhaghighi/clox/compare/v0.4...v0.5
|
|
311
353
|
[0.4]: https://github.com/sepandhaghighi/clox/compare/v0.3...v0.4
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
clox/__init__.py,sha256=gErclFSjUDschQpngWqOBGkBKt1jwd-Ww8B9iJmlU5s,108
|
|
2
|
+
clox/__main__.py,sha256=9oJYc1WXu4ZMrjKny_2-4Cgu46-VWHuE9xOqD1iJY0E,109
|
|
3
|
+
clox/functions.py,sha256=rHrWxzW0NxARlaKaUlO-TXEPyhEG1MQ0jctDxBe192I,10008
|
|
4
|
+
clox/jcalendar.py,sha256=yAzuyXq6h8D5aJG_He_WuPR8Xy411W_l7pl8mtPJa5U,12296
|
|
5
|
+
clox/params.py,sha256=8rlLAfUv3nHNVh5cSsH_1O1aNQoDAgmyKxiTkqV8f54,1654
|
|
6
|
+
clox-0.8.dist-info/AUTHORS.md,sha256=lmtnd18MnfgB57jdvfJbC0JHN3iARf2Ov4pY5kPGJC8,242
|
|
7
|
+
clox-0.8.dist-info/LICENSE,sha256=WoAsqqZ_lNVBGdyxjwh7YnVNXKfOB-qYVrRhrn-e-_4,1072
|
|
8
|
+
clox-0.8.dist-info/METADATA,sha256=-c6IkxbwAxVIm0VY_MU9GICTe9v6xrtErvXEMHOr0iw,9509
|
|
9
|
+
clox-0.8.dist-info/WHEEL,sha256=52BFRY2Up02UkjOa29eZOS2VxUrpPORXg1pkohGGUS8,91
|
|
10
|
+
clox-0.8.dist-info/entry_points.txt,sha256=sP4Rmoe-DxYGjlF_Tld6nghbt_u-fK8h9ZUQFmO8TJs,45
|
|
11
|
+
clox-0.8.dist-info/top_level.txt,sha256=5DxGH-4VNfYkM8vbfngObh6-jpFEoSW4M90EvDGfhSw,5
|
|
12
|
+
clox-0.8.dist-info/RECORD,,
|
clox-0.6.dist-info/RECORD
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
clox/__init__.py,sha256=gErclFSjUDschQpngWqOBGkBKt1jwd-Ww8B9iJmlU5s,108
|
|
2
|
-
clox/__main__.py,sha256=9oJYc1WXu4ZMrjKny_2-4Cgu46-VWHuE9xOqD1iJY0E,109
|
|
3
|
-
clox/functions.py,sha256=PS72c-LD-KzhhzyDHgEcS7yWHCDZuuUz9uCcQWwSKog,7191
|
|
4
|
-
clox/params.py,sha256=A3iNOq5NoZy9D_1lLglJK3vpG1zrk-JLrtKdK5Pc5wY,1497
|
|
5
|
-
clox-0.6.dist-info/AUTHORS.md,sha256=lmtnd18MnfgB57jdvfJbC0JHN3iARf2Ov4pY5kPGJC8,242
|
|
6
|
-
clox-0.6.dist-info/LICENSE,sha256=WoAsqqZ_lNVBGdyxjwh7YnVNXKfOB-qYVrRhrn-e-_4,1072
|
|
7
|
-
clox-0.6.dist-info/METADATA,sha256=2byZTxWOr4x3IvD0ikm5rpJgsEI-v9W_vyigOfsmWk8,8437
|
|
8
|
-
clox-0.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
9
|
-
clox-0.6.dist-info/entry_points.txt,sha256=sP4Rmoe-DxYGjlF_Tld6nghbt_u-fK8h9ZUQFmO8TJs,45
|
|
10
|
-
clox-0.6.dist-info/top_level.txt,sha256=5DxGH-4VNfYkM8vbfngObh6-jpFEoSW4M90EvDGfhSw,5
|
|
11
|
-
clox-0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|