aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO2
-rw-r--r--examples/plugins/leapsec68
-rw-r--r--src/plugins/leapsec.py134
3 files changed, 202 insertions, 2 deletions
diff --git a/TODO b/TODO
index 0e6b0ba..8286919 100644
--- a/TODO
+++ b/TODO
@@ -27,8 +27,6 @@ List of plugins to implement:
Identify active and visible windows
Calendars: holidays, birthdays, name days, events
Clock plugin with timezone support
- Leap second announcement (should work (but tell) if the system does
- not observe leap seconds or uses smear seconds)
Summer time / standard time announcement (should support double
summer time and similar)
natural disaster reports
diff --git a/examples/plugins/leapsec b/examples/plugins/leapsec
new file mode 100644
index 0000000..db70870
--- /dev/null
+++ b/examples/plugins/leapsec
@@ -0,0 +1,68 @@
+# -*- python -*-
+
+# A xpybar configuration example testing the features of plugins.leapsec
+
+import time
+import threading
+
+from plugins.leapsec import LeapSeconds
+from plugins.clock import Clock
+
+
+OUTPUT, HEIGHT, YPOS, TOP = 0, 12, 24, True
+
+
+clock = Clock(sync_to = 0.5 * Clock.SECONDS)
+
+start_ = start
+def start():
+ start_()
+ async(lambda : clock.continuous_sync(lambda : bar.invalidate()))
+
+
+leapsec_ = Sometimes(LeapSeconds, 30 * 24 * 60 * 2)
+
+
+def redraw():
+ announcements = leapsec_()
+ found = -1
+ now = time.time()
+ for index in range(len(announcements)):
+ if announcements[index][3] >= now:
+ found = index
+ break
+ announcement = announcements[found]
+ text = '\033[%sm%s\033[00m ago' if found < 0 else 'in \033[%sm%s\033[00m'
+ def dur(t):
+ s, t = t % 60, t // 60
+ m, t = t % 60, t // 60
+ h, d = t % 24, t // 24
+ if d > 0:
+ return '%id%ih%i\'%02i"' % (d, h, m, s)
+ elif h > 0:
+ return '%ih%i\'%02i"' % (h, m, s)
+ elif m > 0:
+ return '%i\'%02i"' % (m, s)
+ else:
+ return '%02is' % s
+ amount = ('+%i' if announcement[4] > 0 else '%i') % announcement[4]
+ c = '00'
+ time_until = announcement[3] - int(now)
+ if time_until >= 0:
+ if time_until < 10:
+ c = '41'
+ elif time_until < 60:
+ c = '31'
+ elif time_until < 60 * 60:
+ c = '33'
+ elif time_until < 24 * 60 * 60:
+ c = '32'
+ text %= (c, dur(abs(time_until)))
+ text = 'Leap: %s %s at end of UTC %s' % (amount, text, '%i-%02i-%0i' % announcement[:3])
+ if announcement[5] == LeapSeconds.SECONDARY:
+ text += ' (secondary)'
+ if announcement[5] == LeapSeconds.OUT_OF_BAND:
+ text += ' (out of band)'
+ bar.clear()
+ bar.draw_coloured_text(0, 10, 0, 2, text)
+
diff --git a/src/plugins/leapsec.py b/src/plugins/leapsec.py
new file mode 100644
index 0000000..f04ad8d
--- /dev/null
+++ b/src/plugins/leapsec.py
@@ -0,0 +1,134 @@
+# -*- python -*-
+'''
+xpybar – xmobar replacement written in python
+Copyright © 2014 Mattias Andrée (maandree@member.fsf.org)
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import calendar
+
+from util import *
+
+
+class LeapSeconds:
+ '''
+ Leap second announcement monitor
+ '''
+
+
+ PRIMARY = 0
+ '''
+ The leap occurs in a primarily preferred time slot
+ '''
+
+ SECONDARY = 1
+ '''
+ The leap occurs in a secondarily preferred time slot
+ '''
+
+ OUT_OF_BAND = 2
+ '''
+ The leap occurs out of band, not in any preferred time slot
+ '''
+
+
+ def __init__(self):
+ '''
+ Constructor
+ '''
+ url = 'http://maia.usno.navy.mil/ser7/leapsec.dat'
+ announcements = spawn_read('wget', url, '-O', '-')
+ while ' ' in announcements:
+ announcements = announcements.replace(' ', ' ')
+ announcements = announcements.replace('= ', '=').replace('=JD ', '=JD')
+ announcements = [announcement.lstrip().split(' ') for announcement in announcements.split('\n')]
+ test = lambda announcement : announcement.startswith('TAI-UTC=') or announcement.startswith('UTC-TAI=')
+ announcements = [announcement[:3] + list(filter(test, announcement)) for announcement in announcements]
+ MONTHS = { 'JAN' : 1
+ , 'FEB' : 2
+ , 'MAR' : 3
+ , 'APR' : 4
+ , 'MAY' : 5
+ , 'JUN' : 6
+ , 'JUL' : 7
+ , 'AUG' : 8
+ , 'SEP' : 9
+ , 'OCT' : 10
+ , 'NOV' : 11
+ , 'DEC' : 12
+ }
+ DAYS_OF_MONTHS = [-1, 31, 28, 31, 30, 31, 30, 30, 31, 30, 31, 30, 31]
+ def translate(announcement):
+ announcement[0] = int(announcement[0])
+ announcement[1] = MONTHS[announcement[1]]
+ announcement[2] = int(announcement[2]) - 1
+ if announcement[3].startswith('TAI-UTC='):
+ announcement[3] = int(announcement[3].split('=')[1].split('.')[0])
+ else:
+ announcement[3] = -(int(announcement[3].split('=')[1].split('.')[0]))
+ announcement.append(LeapSeconds.OUT_OF_BAND)
+ if announcement[2] == 0:
+ announcement[1] -= 1
+ if announcement[1] == 0:
+ announcement[1] = 12
+ announcement[0] -= 1
+ announcement[2] = DAYS_OF_MONTHS[announcement[1]]
+ if announcement[2] == 2:
+ year = announcement[0]
+ if ((year % 4 == 0) and not (year % 100 == 0)) or (year % 400 == 0):
+ announcement[2] += 1
+ if announcement[1] in (6, 12):
+ announcement[4] = LeapSeconds.PRIMARY
+ elif announcement[1] in (3, 9):
+ announcement[4] = LeapSeconds.SECONDARY
+ return announcement
+ announcements = [translate(announcement) for announcement in announcements]
+ for i in reversed(range(len(announcements) - 1)):
+ announcements[i + 1][3] -= announcements[i][3]
+ self.announcements = announcements[1:]
+
+
+ def __len__(self):
+ '''
+ Get the number of available leap second announcements
+
+ @return The number of available leap second announcements
+ '''
+ return len(self.announcements)
+
+
+ def __getitem__(self, index):
+ '''
+ Get a leap second announcement
+
+ @param index:int? The index of the announcement, negative for reversed chronological indexing
+ @return :(year:int, The year the leap second will occur or occurred (UTC)
+ month:int, The month [1, 12] the leap second will occur or occurred (UTC)
+ day:int, The day [1, 31] the leap second will occur or occurred (UTC)
+ posix:int, The POSIX timestamp the leap will occur or occurred.
+ Keep in mind that POSIX time does not have leap seconds;
+ if `amount == 1` then this time will represent 00:00:00 at
+ the day directly after the day described by (`year`, `month`, `day`)
+ amount:int, The number of leap seconds introduced, can be negative but not zero
+ annon:int) Annonouncement class, either of:
+ LeapSeconds.PRIMARY, LeapSeconds.SECONDARY, LeapSeconds.OUT_OF_BAND
+ '''
+ (y, m, d, a, t) = self.announcements[index]
+ s = 24 * 60 * 60 + a - 1
+ if a > 0:
+ s += 1
+ s = calendar.timegm((y, m, d, 0, 0, s))
+ return (y, m, d, s, a, t)
+