-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f530bcf
Showing
15 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
.idea/ | ||
target/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
jdk: | ||
- oraclejdk8 | ||
language: scala | ||
script: "sbt publish" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# jewish-date | ||
|
||
#### A Scala library for converting between the Gregorian calendar and the Jewish calendar, cross-compiled for JVM and JS | ||
|
||
Heavily based on and derived from https://github.com/KosherJava/zmanim, but taking advantage of the Java 8 `java.time` API, and as a Scala-idiomatic API. | ||
|
||
### Features | ||
|
||
* Convert from `java.time.LocalDate` to `jewishdate.JewishDate` and vice versa | ||
* Get date of Yomim Tovim and Yom Tov of a date (currently Diaspora only; pull requests welcome) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
publishMavenStyle in ThisBuild := true | ||
|
||
publishTo in ThisBuild := Some("Project Bintray" at "https://api.bintray.com/maven/naftoligug/maven/jewish-date") | ||
|
||
sys.env.get("BINTRAYKEY").toSeq.map { key => | ||
credentials in ThisBuild += Credentials( | ||
"Bintray API Realm", | ||
"api.bintray.com", | ||
"naftoligug", | ||
key | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
ThisBuild / organization := "io.github.nafg" | ||
ThisBuild / scalaVersion := "2.12.4" | ||
ThisBuild / scalacOptions ++= Seq("-deprecation", "-unchecked", "-feature") | ||
|
||
lazy val jewishDate = | ||
crossProject.crossType(CrossType.Full) | ||
.in(file(".")) | ||
.settings( | ||
name := "jewish-date", | ||
version := "0.1.0", | ||
libraryDependencies += "io.monix" %%% "minitest" % "2.0.0" % "test", | ||
testFrameworks += new TestFramework("minitest.runner.Framework") | ||
) | ||
.jvmSettings( | ||
libraryDependencies += "org.scalacheck" %% "scalacheck" % "1.13.5" % "test", | ||
libraryDependencies += | ||
("KosherJava" % "zmanim" % "1.4.0alpha" % "test") | ||
.from("https://github.com/KosherJava/zmanim/raw/master/lib/zmanim-1.4.0alpha.jar"), | ||
Test / testOptions += Tests.Argument(TestFrameworks.ScalaCheck, "-minSuccessfulTests", "10000") | ||
) | ||
.jsSettings( | ||
libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.0.0-M12" | ||
) | ||
lazy val jewishDateJS = jewishDate.js | ||
lazy val jewishDateJVM = jewishDate.jvm |
26 changes: 26 additions & 0 deletions
26
jvm/src/test/scala/jewishdate/JewishDateCompanionProps.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package jewishdate | ||
|
||
import java.time.{LocalDate, Month, Year} | ||
import java.util.GregorianCalendar | ||
|
||
import net.sourceforge.zmanim.hebrewcalendar.{JewishCalendar => KJCal} | ||
import org.scalacheck.Prop._ | ||
import org.scalacheck.{Gen, Prop, Properties} | ||
|
||
|
||
class JewishDateCompanionProps extends Properties("JewishDate") { | ||
val genLocalDate: Gen[LocalDate] = | ||
for { | ||
year <- Gen.choose(1, 3000) | ||
month <- Gen.choose(1, 12) | ||
day <- Gen.choose(1, Month.of(month).length(Year.isLeap(year))) | ||
} yield LocalDate.of(year, month, day) | ||
|
||
property("apply") = | ||
Prop.forAll(genLocalDate) { date => | ||
val jc = new KJCal(new GregorianCalendar(date.getYear, date.getMonthValue - 1, date.getDayOfMonth)) | ||
val jd = JewishDate(date) | ||
jd ?= | ||
JewishDate(new JewishYear(jc.getJewishYear), JewishMonth(jc.getJewishMonth), jc.getJewishDayOfMonth) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package jewishdate | ||
|
||
import java.time._ | ||
|
||
import net.sourceforge.zmanim.hebrewcalendar.{JewishCalendar => KJCal} | ||
import org.scalacheck.Prop._ | ||
import org.scalacheck.{Gen, Prop, Properties} | ||
|
||
|
||
class JewishDateProps extends Properties("jewishDate") { | ||
val genJewishDate: Gen[JewishDate] = | ||
for { | ||
year <- Gen.choose(3762, 6000).map(new JewishYear(_)) | ||
month <- Gen.oneOf(year.monthsIterator.toSeq) | ||
day <- Gen.choose(1, year.monthLength(month)) | ||
} yield JewishDate(year, month, day) | ||
|
||
|
||
property("toLocalDate") = | ||
Prop.forAll(genJewishDate) { date => | ||
val jc = new KJCal(date.year.value, date.month.id, date.dayOfMonth) | ||
date.toLocalDate ?= | ||
LocalDate.of(jc.getGregorianYear, jc.getGregorianMonth + 1, jc.getGregorianDayOfMonth) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
sbt.version = 1.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.21") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package jewishdate | ||
|
||
import java.time.LocalDate | ||
|
||
|
||
case class JewishDate(year: JewishYear, month: JewishMonth.Value, dayOfMonth: Int) { | ||
def dayOfYear: Int = { | ||
val priorMonths = year.monthsIterator(JewishMonth.Tishrei).takeWhile(_ != month) | ||
priorMonths.map(year.monthLength).sum + dayOfMonth | ||
} | ||
|
||
def isYomTov(yomTov: YomTov) = { | ||
val d = day - yomTov.startDate.day | ||
d >= 0 && d < yomTov.length | ||
} | ||
|
||
def yomTov = year.yomimTovim.find(isYomTov) | ||
|
||
/** | ||
* This day, counting from the first day of the Jewish calendar | ||
*/ | ||
lazy val day = year.firstDay + dayOfYear | ||
|
||
def next = JewishDate.fromDay(day + 1) | ||
def prev = JewishDate.fromDay(day - 1) | ||
|
||
def toLocalDate: LocalDate = LocalDate.ofEpochDay(day + JewishDate.JewishEpoch) | ||
} | ||
|
||
object JewishDate { | ||
/** | ||
* The start of the Jewish calendar as Epoch Day | ||
*/ | ||
val JewishEpoch = -2092592L | ||
|
||
def apply(year: Int, month: JewishMonth.Value, dayOfMonth: Int, dummy: Null = null): JewishDate = | ||
new JewishDate(new JewishYear(year), month, dayOfMonth) | ||
|
||
def fromDay(jewishDay: Long): JewishDate = { | ||
val jewishYearGuess = new JewishYear((jewishDay / 366).toInt) | ||
val jewishYear = | ||
Iterator.iterate(jewishYearGuess)(_.next) | ||
.dropWhile(_.next.firstDay < jewishDay) | ||
.next | ||
|
||
val monthSearchStart = | ||
if (jewishDay < new JewishDate(jewishYear, JewishMonth.Nissan, 1).day) | ||
JewishMonth.Tishrei | ||
else | ||
JewishMonth.Nissan | ||
val jewishMonth = | ||
jewishYear.monthsIterator(monthSearchStart) | ||
.dropWhile(m => jewishDay > new JewishDate(jewishYear, m, jewishYear.monthLength(m)).day) | ||
.next | ||
val firstDayOfMonth = new JewishDate(jewishYear, jewishMonth, 1) | ||
val actualDayOfMonth = jewishDay - firstDayOfMonth.day + 1 | ||
firstDayOfMonth.copy(dayOfMonth = actualDayOfMonth.toInt) | ||
} | ||
|
||
def apply(localDate: LocalDate): JewishDate = fromDay(localDate.toEpochDay - JewishEpoch) | ||
|
||
implicit val ordering: Ordering[JewishDate] = Ordering.by(_.day) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package jewishdate | ||
|
||
object JewishMonth extends Enumeration(1) { | ||
val Nissan, Iyar, Sivan, Tammuz, Av, Elul, Tishrei, Cheshvan, Kislev, Teves, Shvat, Adar = Value | ||
val `Adar Sheni` = Value("Adar Sheni") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
package jewishdate | ||
|
||
import java.time.DayOfWeek | ||
|
||
|
||
class JewishYear(val value: Int) extends AnyVal { | ||
override def toString = value.toString | ||
|
||
def next = new JewishYear(value + 1) | ||
|
||
def prev = new JewishYear(value - 1) | ||
|
||
def isLeap = (7 * value + 1) % 19 < 7 | ||
|
||
def numMonths = if (isLeap) 13 else 12 | ||
|
||
/** | ||
* The first day of this year, counting from the first day of the Jewish calendar | ||
*/ | ||
def firstDay = { | ||
val monthsPriorMetonicCycles = 235 * ((value - 1) / 19) | ||
val nonLeapMonthsThisCycle = 12 * ((value - 1) % 19) | ||
val leapMonthsThisCycle = (7 * ((value - 1) % 19) + 1) / 19 | ||
val monthsPassed = monthsPriorMetonicCycles + nonLeapMonthsThisCycle + leapMonthsThisCycle | ||
val chalakimFromMoladTohu = JewishYear.ChalakimMoladTohu + JewishYear.ChalakimPerMonth * monthsPassed.toLong | ||
val moladDay = (chalakimFromMoladTohu / JewishYear.ChalakimPerDay).toInt | ||
val moladChalakimInDay = (chalakimFromMoladTohu - moladDay.toLong * JewishYear.ChalakimPerDay).toInt | ||
|
||
// GaTRaD - If on a non leap year, the molad of Tishrei falls on a Tuesday (Ga) on or after 9 hours (T) and 204 | ||
// chalakim (RaD) it is delayed till Thursday (one day delay, plus one day for Lo ADU Rosh) | ||
val dechiyaGaTRaD = !isLeap && moladDay % 7 == 2 && moladChalakimInDay >= 9924 | ||
|
||
// BeTuTaKFoT - if the year following a leap year falls on a Monday (Be) on or after 15 hours (Tu) and 589 | ||
// chalakim (TaKFoT) it is delayed till Tuesday | ||
val dechiyaBeTuTaKFot = prev.isLeap && moladDay % 7 == 1 && moladChalakimInDay >= 16789 | ||
|
||
// Molad Zaken - If the molad of Tishrei falls after 12 noon, Rosh Hashana is delayed to the following day. If | ||
// the following day is ADU, it will be delayed an additional day. | ||
val dechiyaMoladZaken = moladChalakimInDay >= 19440 | ||
|
||
val day0 = if (dechiyaMoladZaken || dechiyaGaTRaD || dechiyaBeTuTaKFot) moladDay + 1 else moladDay | ||
|
||
// Lo ADU Rosh - Rosh Hashana can't fall on a Sunday, Wednesday or Friday. If the molad fell on one of these | ||
// days, Rosh Hashana is delayed to the following day. | ||
val dechiyaLoADURosh = day0 % 7 == 0 || day0 % 7 == 3 || day0 % 7 == 5 | ||
|
||
val withDechiyos = if (dechiyaLoADURosh) day0 + 1 else day0 | ||
|
||
withDechiyos | ||
} | ||
|
||
/** | ||
* The number of days in this Jewish year | ||
*/ | ||
def length = next.firstDay - firstDay | ||
|
||
def isCheshvanLong = length % 10 == 5 | ||
|
||
def isKislevLong = length % 10 != 3 | ||
|
||
/** | ||
* Returns an Iterator of the months of this year, starting from Nissan | ||
*/ | ||
def monthsIterator: Iterator[JewishMonth.Value] = JewishMonth.values.iterator.take(numMonths) | ||
|
||
/** | ||
* Retuns an Iterator of the months of this year, starting from the given month. | ||
*/ | ||
def monthsIterator(first: JewishMonth.Value): Iterator[JewishMonth.Value] = { | ||
val (before, fromStart) = monthsIterator.span(_ != first) | ||
fromStart ++ before | ||
} | ||
|
||
def monthLength(month: JewishMonth.Value) = month match { | ||
case JewishMonth.Nissan | JewishMonth.Sivan | JewishMonth.Av | JewishMonth.Tishrei | JewishMonth.Shvat => 30 | ||
case JewishMonth.Cheshvan if isCheshvanLong => 30 | ||
case JewishMonth.Kislev if isKislevLong => 30 | ||
case JewishMonth.Adar if isLeap => 30 | ||
case _ => 29 | ||
} | ||
|
||
private def date(month: JewishMonth.Value, dayOfMonth: Int) = JewishDate(this, month, dayOfMonth) | ||
|
||
def pesachFirst = YomTov("Pesach (first days)", date(JewishMonth.Nissan, 15), 2, melachaForbidden = true) | ||
def pesachCholHamoed = YomTov("Chol Hamoed Pesach", date(JewishMonth.Nissan, 17), 4, melachaForbidden = false) | ||
def pesachLast = YomTov("Pesach (last days)", date(JewishMonth.Nissan, 21), 2, melachaForbidden = true) | ||
def shavuos = YomTov("Shavuos", date(JewishMonth.Sivan, 6), 2, melachaForbidden = true) | ||
def tishaBAv = { | ||
val d = date(JewishMonth.Av, 9) | ||
val d2 = if (d.toLocalDate.getDayOfWeek == DayOfWeek.SATURDAY) d.next else d | ||
YomTov("Tisha B'Av", d2, 1, melachaForbidden = false) | ||
} | ||
def roshHashanah = YomTov("Rosh Hashanah", date(JewishMonth.Tishrei, 1), 2, melachaForbidden = true) | ||
def yomKippur = YomTov("Yom Kippur", date(JewishMonth.Tishrei, 10), 1, melachaForbidden = true) | ||
def sukkosFirst = YomTov("Sukkos (first days)", date(JewishMonth.Tishrei, 15), 2, melachaForbidden = true) | ||
def sukkosCholHamoed = | ||
YomTov("Chol Hamoed Sukkos", date(JewishMonth.Tishrei, 17), 4, melachaForbidden = false) | ||
def hoshanahRabbah = YomTov("Hoshanah Rabbah", date(JewishMonth.Tishrei, 21), 1, melachaForbidden = false) | ||
def sheminiAtzeres = YomTov("Shemini Atzeres", date(JewishMonth.Tishrei, 22), 1, melachaForbidden = true) | ||
def simchasTorah = YomTov("Simchas Torah", date(JewishMonth.Tishrei, 23), 1, melachaForbidden = true) | ||
def chanukah = YomTov("Chanukah", date(JewishMonth.Kislev, 25), 8, melachaForbidden = false) | ||
def purim = | ||
YomTov("Purim", date(if (isLeap) JewishMonth.`Adar Sheni` else JewishMonth.Adar, 14), 1, melachaForbidden = false) | ||
def shushanPurim = | ||
YomTov("Shushan Purim", date(if (isLeap) JewishMonth.`Adar Sheni` else JewishMonth.Adar, 15), 1, melachaForbidden = false) | ||
|
||
def yomimTovim = { | ||
Seq( | ||
pesachFirst, | ||
pesachCholHamoed, | ||
pesachLast, | ||
shavuos, | ||
roshHashanah, | ||
yomKippur, | ||
sukkosFirst, | ||
sukkosCholHamoed, | ||
hoshanahRabbah, | ||
sheminiAtzeres, | ||
simchasTorah, | ||
chanukah, | ||
purim, | ||
shushanPurim | ||
) | ||
} | ||
} | ||
|
||
object JewishYear { | ||
val ChalakimMoladTohu = 31524L | ||
val ChalakimPerMonth = 765433L | ||
val ChalakimPerDay = 25920L | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package jewishdate | ||
|
||
case class YomTov(name: String, startDate: JewishDate, length: Int, melachaForbidden: Boolean) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package jewishdate | ||
|
||
import java.time.LocalDate | ||
|
||
import minitest.SimpleTestSuite | ||
|
||
|
||
object ConversionExamples extends SimpleTestSuite { | ||
test("Example 1") { | ||
assertEquals(JewishDate(LocalDate.of(842, 3, 31)), JewishDate(4602, JewishMonth.Nissan, 12)) | ||
} | ||
test("Example 2") { | ||
assertEquals(JewishDate(LocalDate.of(1582, 10, 11)), JewishDate(5343, JewishMonth.Tishrei, 15)) | ||
} | ||
test("Example 3") { | ||
assertEquals(JewishDate(LocalDate.of(1582, 10, 14)), JewishDate(5343, JewishMonth.Tishrei, 18)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package jewishdate | ||
|
||
import minitest.SimpleTestSuite | ||
|
||
|
||
object YomTovExamples extends SimpleTestSuite { | ||
test("Yomim Tovim") { | ||
val year = new JewishYear(5778) | ||
assert(JewishDate(year, JewishMonth.Nissan, 22).isYomTov(year.pesachLast)) | ||
assert(JewishDate(year, JewishMonth.Nissan, 23).yomTov.isEmpty) | ||
} | ||
} |