Compare commits
16 Commits
513e25b3df
...
v1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| b9072fc8e8 | |||
| 7763284ae3 | |||
| 0873432a96 | |||
| 838069e1b9 | |||
| d71abe29b0 | |||
| 576ab90180 | |||
| 4a91c4bbc9 | |||
| 2db714d353 | |||
| 23a8fc5f3e | |||
| ea5b262610 | |||
| b7078f4e5d | |||
| 0908275eba | |||
| 0a0a602a11 | |||
| b8e69b3549 | |||
| 909a29d5e3 | |||
| 668fc0f63d |
@@ -24,7 +24,7 @@ mvn package
|
|||||||
Using Maven:
|
Using Maven:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mvn exec:java@com.calendar.CalendarApp
|
mvn exec:java
|
||||||
```
|
```
|
||||||
|
|
||||||
Using Java directly:
|
Using Java directly:
|
||||||
|
|||||||
6
pom.xml
6
pom.xml
@@ -29,6 +29,12 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
public class CalendarPanel extends JPanel {
|
public class CalendarPanel extends JPanel {
|
||||||
|
private PointEngine pointEngine;
|
||||||
private YearMonth currentMonth;
|
private YearMonth currentMonth;
|
||||||
private final int cellHeight = 60;
|
private final int cellHeight = 60;
|
||||||
private final int cellWidth = 80;
|
private final int cellWidth = 80;
|
||||||
private final Set<LocalDate> selectedDates = new HashSet<>();
|
private final Set<LocalDate> selectedDates = new HashSet<>();
|
||||||
|
|
||||||
public CalendarPanel() {
|
public CalendarPanel(PointEngine pointEngine) {
|
||||||
|
this.pointEngine = pointEngine;
|
||||||
this.currentMonth = YearMonth.now();
|
this.currentMonth = YearMonth.now();
|
||||||
setPreferredSize(new Dimension(7 * cellWidth, 8 * cellHeight));
|
setPreferredSize(new Dimension(7 * cellWidth, 8 * cellHeight));
|
||||||
setBackground(Color.WHITE);
|
setBackground(Color.WHITE);
|
||||||
@@ -67,6 +69,9 @@ public class CalendarPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void toggleSelection(LocalDate date) {
|
private void toggleSelection(LocalDate date) {
|
||||||
|
if (pointEngine.getPointsOfDay(date) == 0.0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (selectedDates.contains(date)) {
|
if (selectedDates.contains(date)) {
|
||||||
selectedDates.remove(date);
|
selectedDates.remove(date);
|
||||||
} else {
|
} else {
|
||||||
@@ -77,7 +82,13 @@ public class CalendarPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public double getTotalPoints() {
|
public double getTotalPoints() {
|
||||||
return selectedDates.size() * 1.0;
|
double totalPoints = 0.0;
|
||||||
|
|
||||||
|
for (LocalDate date : selectedDates) {
|
||||||
|
totalPoints += pointEngine.getPointsOfDay(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalPoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalDate getDateAtPoint(int x, int y) {
|
private LocalDate getDateAtPoint(int x, int y) {
|
||||||
@@ -148,13 +159,18 @@ public class CalendarPanel extends JPanel {
|
|||||||
LocalDate cellDate = currentMonth.atDay(day);
|
LocalDate cellDate = currentMonth.atDay(day);
|
||||||
boolean isToday = cellDate.equals(today);
|
boolean isToday = cellDate.equals(today);
|
||||||
boolean isSelected = selectedDates.contains(cellDate);
|
boolean isSelected = selectedDates.contains(cellDate);
|
||||||
|
double points = pointEngine.getPointsOfDay(cellDate);
|
||||||
|
|
||||||
if (isSelected) {
|
if (points == 0.0) {
|
||||||
|
g.setColor(new Color(255, 100, 100));
|
||||||
|
g.fillRect(x, y, cellWidth, cellHeight);
|
||||||
|
} else if (isSelected && isToday) {
|
||||||
|
g.setColor(new Color(123, 166, 180));
|
||||||
|
g.fillRect(x, y, cellWidth, cellHeight);
|
||||||
|
} else if (isSelected) {
|
||||||
g.setColor(new Color(173, 216, 230));
|
g.setColor(new Color(173, 216, 230));
|
||||||
g.fillRect(x, y, cellWidth, cellHeight);
|
g.fillRect(x, y, cellWidth, cellHeight);
|
||||||
}
|
} else if (isToday) {
|
||||||
|
|
||||||
if (isToday) {
|
|
||||||
g.setColor(new Color(255, 200, 100));
|
g.setColor(new Color(255, 200, 100));
|
||||||
g.fillRect(x, y, cellWidth, cellHeight);
|
g.fillRect(x, y, cellWidth, cellHeight);
|
||||||
}
|
}
|
||||||
@@ -167,7 +183,7 @@ public class CalendarPanel extends JPanel {
|
|||||||
int textY = y + (cellHeight - fm.getHeight()) / 2 + fm.getAscent() - 8;
|
int textY = y + (cellHeight - fm.getHeight()) / 2 + fm.getAscent() - 8;
|
||||||
g.drawString(dayStr, textX, textY);
|
g.drawString(dayStr, textX, textY);
|
||||||
|
|
||||||
String pointsStr = "(1.0)";
|
String pointsStr = "(" + points + ")";
|
||||||
Font smallFont = new Font("Arial", Font.PLAIN, 10);
|
Font smallFont = new Font("Arial", Font.PLAIN, 10);
|
||||||
FontMetrics smallFm = g.getFontMetrics(smallFont);
|
FontMetrics smallFm = g.getFontMetrics(smallFont);
|
||||||
g.setFont(smallFont);
|
g.setFont(smallFont);
|
||||||
|
|||||||
110
src/main/java/net/themusicinnoise/vaccalc/PointEngine.java
Normal file
110
src/main/java/net/themusicinnoise/vaccalc/PointEngine.java
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
package net.themusicinnoise.vaccalc;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.Month;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class PointEngine {
|
||||||
|
private double defaultPoints;
|
||||||
|
|
||||||
|
static private class PointRule {
|
||||||
|
private enum RuleType {
|
||||||
|
DAY_OF_WEEK,
|
||||||
|
MONTH,
|
||||||
|
DATE,
|
||||||
|
}
|
||||||
|
|
||||||
|
static final Pattern DOW_PATTERN = Pattern.compile("dow=(sun|mon|tue|wed|thu|fri|sat) (\\d+\\.\\d+)", Pattern.CASE_INSENSITIVE);
|
||||||
|
static final Pattern MONTH_PATTERN = Pattern.compile("m=(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) (\\d+\\.\\d+)", Pattern.CASE_INSENSITIVE);
|
||||||
|
static final Pattern DATE_PATTERN = Pattern.compile("(\\d{4}-\\d{2}-\\d{2}) (\\d+\\.\\d+)", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
private RuleType type;
|
||||||
|
private DayOfWeek dow;
|
||||||
|
private Month mon;
|
||||||
|
private LocalDate date;
|
||||||
|
private double points;
|
||||||
|
|
||||||
|
public PointRule(String def) {
|
||||||
|
Matcher dowMatcher = DOW_PATTERN.matcher(def);
|
||||||
|
Matcher monthMatcher = MONTH_PATTERN.matcher(def);
|
||||||
|
Matcher dateMatcher = DATE_PATTERN.matcher(def);
|
||||||
|
|
||||||
|
if (dowMatcher.find()) {
|
||||||
|
this.type = RuleType.DAY_OF_WEEK;
|
||||||
|
this.points = Double.parseDouble(dowMatcher.group(2));
|
||||||
|
this.dow = DayOfWeek.from(DateTimeFormatter.ofPattern("EEE").parse(dowMatcher.group(1)));
|
||||||
|
} else if (monthMatcher.find()) {
|
||||||
|
this.type = RuleType.MONTH;
|
||||||
|
this.points = Double.parseDouble(monthMatcher.group(2));
|
||||||
|
this.mon = Month.from(DateTimeFormatter.ofPattern("MMM").parse(monthMatcher.group(1)));
|
||||||
|
} else if (dateMatcher.find()) {
|
||||||
|
this.type = RuleType.DATE;
|
||||||
|
this.points = Double.parseDouble(dateMatcher.group(2));
|
||||||
|
this.date = LocalDate.parse(dateMatcher.group(1), DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Invalid line: '" + def + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean applies(LocalDate date) {
|
||||||
|
switch (type) {
|
||||||
|
case DAY_OF_WEEK:
|
||||||
|
return this.dow == date.getDayOfWeek();
|
||||||
|
case MONTH:
|
||||||
|
return this.mon == date.getMonth();
|
||||||
|
case DATE:
|
||||||
|
return this.date.isEqual(date);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getPoints() { return points; }
|
||||||
|
}
|
||||||
|
private List<PointRule> rules;
|
||||||
|
|
||||||
|
public PointEngine() {
|
||||||
|
defaultPoints = 1.0;
|
||||||
|
rules = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void importPointsFile(File pointsFile) {
|
||||||
|
Pattern defaultPattern = Pattern.compile("default (\\d+\\.\\d+)");
|
||||||
|
|
||||||
|
try (BufferedReader br = new BufferedReader(new FileReader(pointsFile))) {
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
if (line.isEmpty() || line.charAt(0) == '#')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Matcher defaultMatcher = defaultPattern.matcher(line);
|
||||||
|
if (defaultMatcher.find()) {
|
||||||
|
defaultPoints = Double.parseDouble(defaultMatcher.group(1));
|
||||||
|
} else {
|
||||||
|
rules.addFirst(new PointRule(line));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to read points file: " + pointsFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() { rules.clear(); }
|
||||||
|
|
||||||
|
public double getPointsOfDay(LocalDate date) {
|
||||||
|
for (PointRule rule : rules) {
|
||||||
|
if (rule.applies(date)) {
|
||||||
|
return rule.getPoints();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultPoints;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,17 @@ package net.themusicinnoise.vaccalc;
|
|||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.event.*;
|
||||||
|
import java.io.*;
|
||||||
import java.time.YearMonth;
|
import java.time.YearMonth;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class VacCalc extends JFrame {
|
public class VacCalc extends JFrame {
|
||||||
private CalendarPanel calendarPanel;
|
private CalendarPanel calendarPanel;
|
||||||
private JLabel monthLabel;
|
private JLabel monthLabel;
|
||||||
private JLabel pointsLabel;
|
private JLabel pointsLabel;
|
||||||
|
private PointEngine pointEngine;
|
||||||
|
|
||||||
public VacCalc() {
|
public VacCalc() {
|
||||||
setTitle("VacCalc");
|
setTitle("VacCalc");
|
||||||
@@ -15,7 +20,77 @@ public class VacCalc extends JFrame {
|
|||||||
setResizable(false);
|
setResizable(false);
|
||||||
setLocationRelativeTo(null);
|
setLocationRelativeTo(null);
|
||||||
|
|
||||||
calendarPanel = new CalendarPanel();
|
JMenuBar menuBar = new JMenuBar();
|
||||||
|
JMenu appMenu = new JMenu("VacCalc");
|
||||||
|
JMenuItem importItem = new JMenuItem("Import points");
|
||||||
|
importItem.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
JFileChooser fileChooser = new JFileChooser();
|
||||||
|
int ret = fileChooser.showOpenDialog(VacCalc.this);
|
||||||
|
if(ret == JFileChooser.APPROVE_OPTION) {
|
||||||
|
try {
|
||||||
|
pointEngine.importPointsFile(fileChooser.getSelectedFile());
|
||||||
|
} catch(RuntimeException ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
JOptionPane.showMessageDialog(VacCalc.this, ex.getMessage(), "Parsing Error",
|
||||||
|
JOptionPane.ERROR_MESSAGE);
|
||||||
|
pointEngine.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
calendarPanel.repaint();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
appMenu.add(importItem);
|
||||||
|
JMenuItem exitItem = new JMenuItem("Exit");
|
||||||
|
exitItem.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent e) {
|
||||||
|
System.exit(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
appMenu.add(exitItem);
|
||||||
|
menuBar.add(appMenu);
|
||||||
|
JMenu helpMenu = new JMenu("Help");
|
||||||
|
JMenuItem manualItem = new JMenuItem("Usage Manual");
|
||||||
|
manualItem.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("manual.html")));
|
||||||
|
String manualText = br.lines().collect(Collectors.joining());
|
||||||
|
JEditorPane textArea = new JEditorPane("text/html", manualText);
|
||||||
|
textArea.setEditable(false);
|
||||||
|
JFrame manualFrame = new JFrame("VacCalc Manual");
|
||||||
|
manualFrame.getContentPane().add(new JScrollPane(textArea));
|
||||||
|
manualFrame.setSize(500, 500);
|
||||||
|
manualFrame.setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
helpMenu.add(manualItem);
|
||||||
|
JMenuItem aboutItem = new JMenuItem("About");
|
||||||
|
aboutItem.addActionListener(new ActionListener() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(ActionEvent ev) {
|
||||||
|
BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream("about.html")));
|
||||||
|
Properties properties = new Properties();
|
||||||
|
try {
|
||||||
|
properties.load(getClass().getClassLoader().getResourceAsStream("project.properties"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("Failed to load project properties.");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
String aboutText = br.lines().collect(Collectors.joining());
|
||||||
|
aboutText = aboutText.replace("VERSION", properties.getProperty("version"));
|
||||||
|
JOptionPane.showMessageDialog(VacCalc.this, aboutText, "About",
|
||||||
|
JOptionPane.INFORMATION_MESSAGE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
helpMenu.add(aboutItem);
|
||||||
|
menuBar.add(helpMenu);
|
||||||
|
setJMenuBar(menuBar);
|
||||||
|
|
||||||
|
pointEngine = new PointEngine();
|
||||||
|
calendarPanel = new CalendarPanel(pointEngine);
|
||||||
|
|
||||||
JPanel headerPanel = createHeaderPanel();
|
JPanel headerPanel = createHeaderPanel();
|
||||||
JPanel footerPanel = createFooterPanel();
|
JPanel footerPanel = createFooterPanel();
|
||||||
@@ -87,14 +162,14 @@ public class VacCalc extends JFrame {
|
|||||||
|
|
||||||
private void updateMonthLabel() {
|
private void updateMonthLabel() {
|
||||||
YearMonth current = calendarPanel.getCurrentMonth();
|
YearMonth current = calendarPanel.getCurrentMonth();
|
||||||
monthLabel.setText(String.format("%s %d",
|
monthLabel.setText(String.format("%s %d",
|
||||||
current.getMonth().toString(),
|
current.getMonth().toString(),
|
||||||
current.getYear()));
|
current.getYear()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updatePointsLabel() {
|
private void updatePointsLabel() {
|
||||||
double points = calendarPanel.getTotalPoints();
|
double points = calendarPanel.getTotalPoints();
|
||||||
pointsLabel.setText(String.format("Total Points: %.1f", points));
|
pointsLabel.setText(String.format("Total Points: %.3f", points));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
|||||||
7
src/main/resources/about.html
Normal file
7
src/main/resources/about.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<html>
|
||||||
|
<p><b>VacCalc VERSION</b></p>
|
||||||
|
<hr />
|
||||||
|
<p>Copyright © 2026 Nicolás A. Ortega Froysa <nicolas@ortegas.org></p>
|
||||||
|
<p>License: Zlib</p>
|
||||||
|
<p>Home page: <a href="https://code.ortegas.org/nortega/vaccalc" >https://code.ortegas.org/nortega/vaccalc</a></p>
|
||||||
|
</html>
|
||||||
59
src/main/resources/manual.html
Normal file
59
src/main/resources/manual.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<html>
|
||||||
|
<h1>VacCalc Manual</h1>
|
||||||
|
<hr />
|
||||||
|
<p>VacCalc assigns points to each day on the basis of the rules found in a
|
||||||
|
file which is imported via the “VacCalc > Import points” menu item. This
|
||||||
|
file must be written manually. What follows are descriptions on how to write
|
||||||
|
this file.</p>
|
||||||
|
<h2>Rules</h2>
|
||||||
|
<p>There are a total of four different kinds of rules possible: default,
|
||||||
|
date, day-of-week, and month. Rules are applied with a priority for the last
|
||||||
|
defined rule, with the exception of the default rule which is only used if
|
||||||
|
no other rule applies.</p>
|
||||||
|
<h3>Default Rule</h3>
|
||||||
|
<p>Definition: <code>default <i>points</i></code></p>
|
||||||
|
<p>The default rule establishes the default point value for every day of the
|
||||||
|
year. E.g. <code>default 1.0</code> sets the default points to
|
||||||
|
<code>1.0</code>.
|
||||||
|
<h3>Date Rule</h3>
|
||||||
|
<p>Definition: <code><i>YYYY</i>-<i>MM</i>-<i>DD</i> <i>points</i></code></p>
|
||||||
|
<p>A date rule specifies the points which a specific date has. E.g.
|
||||||
|
<code>2026-12-24 0.5</code> would set Christmas Eve (December
|
||||||
|
24<sup>th</sup>) of 2026 to <code>0.5</code> points</p>
|
||||||
|
<h3>Day-Of-Week Rule</h3>
|
||||||
|
<p>Definition: <code>dow=<i>day</i> <i>points</i></p>
|
||||||
|
<p>A day-of-week rule specifies the points for all days of the year which
|
||||||
|
coincide with a particular day of the week. This day of the week is
|
||||||
|
specified using the three-letter format, such as <code>Fri</code> for
|
||||||
|
Friday. E.g. <code>dow=Fri 0.8</code> would set all Fridays of the year to
|
||||||
|
<code>0.8</code> points.</p>
|
||||||
|
<h3>Month Rule</h3>
|
||||||
|
<p>Definition: <code>m=<i>month</i> <i>points</i></p>
|
||||||
|
<p>A month rule establishes the same points for every day of a given month.
|
||||||
|
E.g. <code>m=Aug 0.8</code> sets all the days of the month of August to
|
||||||
|
<code>0.8</code> points.
|
||||||
|
<h3>Zero Point Rules</h3>
|
||||||
|
<p>Rules that have zero points allocated to them (i.e. <code>0.0</code>) are
|
||||||
|
considered <em>free days</em>, and as such are disabled. This could be
|
||||||
|
assigned to days such as Christmas or the Weekends (Saturday and
|
||||||
|
Sunday).</p>
|
||||||
|
<h3>Comments</h3>
|
||||||
|
<p>Definition: <code># <i>comment</i></p>
|
||||||
|
<p>Comments are supported in single lines starting with the <code>#</code>
|
||||||
|
character. Everything after that is ignored by the parser.</p>
|
||||||
|
<h3>Example</h3>
|
||||||
|
<code>
|
||||||
|
default 1.0<br />
|
||||||
|
dow=Fri 0.825<br />
|
||||||
|
m=Jul 0.825<br />
|
||||||
|
m=Aug 0.825<br />
|
||||||
|
2026-12-24 0.5<br />
|
||||||
|
2026-12-31 0.5<br />
|
||||||
|
<br />
|
||||||
|
# Weekends and Holidays<br />
|
||||||
|
dow=Sun 0.0<br />
|
||||||
|
dow=Sat 0.0<br />
|
||||||
|
2026-01-01 0.0<br />
|
||||||
|
2026-12-25 0.0
|
||||||
|
</code>
|
||||||
|
</html>
|
||||||
1
src/main/resources/project.properties
Normal file
1
src/main/resources/project.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
version=${project.version}
|
||||||
12
vac-points.txt
Normal file
12
vac-points.txt
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
default 1.0
|
||||||
|
dow=Fri 0.825
|
||||||
|
m=Jul 0.825
|
||||||
|
m=Aug 0.825
|
||||||
|
2026-12-24 0.5
|
||||||
|
2026-12-31 0.5
|
||||||
|
|
||||||
|
# Weekends & Holidays
|
||||||
|
dow=Sun 0.0
|
||||||
|
dow=Sat 0.0
|
||||||
|
2026-01-01 0.0
|
||||||
|
2026-12-25 0.0
|
||||||
Reference in New Issue
Block a user