diff --git a/README.md b/README.md index 711a9fd..a61165c 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,19 @@ twitter-bootstrap widget [available from `npm`](https://www.npmjs.com/package/bi require('bikram-sambat-bootstrap'); For usage example, see `bootstrap/dist`. + +# Development + +## Run tests + +1. Install android sdk +2. Execute `make test` + +## Update compressed days in month data + +1. Update `/test-data/daysInMonth.json` as required +2. The first entry in `/test-data/daysInMonth.json` as the BS Epoch. Take this + and convert it to AD and update the bsEpoch constant below. +3. Run the script: node ./scripts/encode-days-in-month.js +4. Copy the output code into the files overwriting the existing hardcoded values + diff --git a/java/lib/src/main/java/bikramsambat/BsCalendar.java b/java/lib/src/main/java/bikramsambat/BsCalendar.java index df446cb..724ff45 100644 --- a/java/lib/src/main/java/bikramsambat/BsCalendar.java +++ b/java/lib/src/main/java/bikramsambat/BsCalendar.java @@ -16,19 +16,17 @@ public final class BsCalendar { - // We have defined our own Epoch for Bikram Sambat: - // 1-1-2007 BS / 13-4-1950 AD - private static final long MS_PER_DAY = 86400000L; - private static final long BS_EPOCH_TS = -622359900000L; // 1950-4-13 AD - private static final long BS_YEAR_ZERO = 2007L; + // ------ TO UPDATE THESE HARDCODED VALUES USE /scripts/encode-days-in-month.js + // We have defined our own Epoch for Bikram Sambat: 1970-1-1 BS or 1913-4-13 AD + private static final long BS_EPOCH_TS = -1789990200000L; // 1913-4-13 AD + private static final int BS_YEAR_ZERO = 1970; + private static final long[] ENCODED_MONTH_LENGTHS = { + 5315258L,5314490L,9459438L,8673005L,5315258L,5315066L,9459438L,8673005L,5315258L,5314298L,9459438L,5327594L,5315258L,5314298L,9459438L,5327594L,5315258L,5314286L,9459438L,5315306L,5315258L,5314286L,8673006L,5315306L,5315258L,5265134L,8673006L,5315258L,5315258L,9459438L,8673005L,5315258L,5314298L,9459438L,8673005L,5315258L,5314298L,9459438L,8473322L,5315258L,5314298L,9459438L,5327594L,5315258L,5314298L,9459438L,5327594L,5315258L,5314286L,8673006L,5315306L,5315258L,5265134L,8673006L,5315306L,5315258L,9459438L,8673005L,5315258L,5314490L,9459438L,8673005L,5315258L,5314298L,9459438L,8473325L,5315258L,5314298L,9459438L,5327594L,5315258L,5314298L,9459438L,5327594L,5315258L,5314286L,9459438L,5315306L,5315258L,5265134L,8673006L,5315306L,5315258L,5265134L,8673006L,5315258L,5314490L,9459438L,8673005L,5315258L,5314298L,9459438L,8669933L,5315258L,5314298L,9459438L,8473322L,5315258L,5314298L,9459438L,5327594L,5315258L,5314286L,9459438L,5315306L,5315258L,5265134L,8673006L,5315306L,5315258L,5265134L,5527290L,5527277L,5527226L,5527226L,5528046L,5527277L,5528250L,5528057L,5527277L,5527277L + }; private static final BsCalendar instance = new BsCalendar(); - private static final TimeZone GMT = TimeZone.getTimeZone("GMT"); - - private static final long[] ENCODED_MONTH_LENGTHS = { - 8673005L,5315258L,5314298L,9459438L,8673005L,5315258L,5314298L,9459438L,8473322L,5315258L,5314298L,9459438L,5327594L,5315258L,5314298L,9459438L,5327594L,5315258L,5314286L,8673006L,5315306L,5315258L,5265134L,8673006L,5315306L,5315258L,9459438L,8673005L,5315258L,5314490L,9459438L,8673005L,5315258L,5314298L,9459438L,8473325L,5315258L,5314298L,9459438L,5327594L,5315258L,5314298L,9459438L,5327594L,5315258L,5314286L,9459438L,5315306L,5315258L,5265134L,8673006L,5315306L,5315258L,5265134L,8673006L,5315258L,5314490L,9459438L,8673005L,5315258L,5314298L,9459438L,8669933L,5315258L,5314298L,9459438L,8473322L,5315258L,5314298L,9459438L,5327594L,5315258L,5314286L,9459438L,5315306L,5315258L,5265134L,8673006L,5315306L,5315258L,5265134L,5527290L,5527277L,5527226L,5527226L,5528046L,5527277L,5528250L,5528057L,5527277L,5527277L, - }; + private static final long MS_PER_DAY = 86400000L; private static final String[] _MONTH_NAMES = { "बैशाख", "जेठ", "असार", "साउन", "भदौ", "असोज", "कार्तिक", "मंसिर", "पौष", "माघ", "फाल्गुन", "चैत" }; public static final List MONTH_NAMES = unmodifiableList(asList(_MONTH_NAMES)); @@ -40,14 +38,14 @@ public final class BsCalendar { /** * Magic numbers: - * 2000 <- the first year encoded in ENCODED_MONTH_LENGTHS + * BS_YEAR_ZERO <- the first year encoded in ENCODED_MONTH_LENGTHS * month #5 <- this is the only month which has a day variation of more than 1 * & 3 <- this is a 2 bit mask, i.e. 0...011 */ public int daysInMonth(int year, int month) throws BsException { if(month < 1 || month > 12) throw new BsException(format("Month does not exist: %s", month)); try { - return 29 + (int) ((ENCODED_MONTH_LENGTHS[year - 2000] >>> + return 29 + (int) ((ENCODED_MONTH_LENGTHS[year - BS_YEAR_ZERO] >>> (((month-1) << 1))) & 3); } catch(ArrayIndexOutOfBoundsException ex) { throw new BsException(format("Unsupported year/month combination: %s/%s", year, month)); @@ -57,16 +55,16 @@ public int daysInMonth(int year, int month) throws BsException { public BsGregorianDate toGreg(BikramSambatDate bik) throws BsException { int year = bik.year, month = bik.month, day = bik.day; - long timestamp = BS_EPOCH_TS; + long timestamp = BS_EPOCH_TS + (MS_PER_DAY * day); + month--; + while(year >= BS_YEAR_ZERO) { - while(month >= 1) { - while(--day >= 0) { - timestamp += MS_PER_DAY; - } - if(--month == 0) break; - day = daysInMonth(year, month); + while(month > 0) { + timestamp += (MS_PER_DAY * daysInMonth(year, month)); + month--; } - day = daysInMonth(--year, month = 12); + month = 12; + year--; } Calendar c = Calendar.getInstance(GMT); @@ -99,11 +97,11 @@ public BikramSambatDate toBik(int year, int month, int day) throws BsException { /** * Magic numbers: * 86400000 <- the number of miliseconds in a day - * 2007 <- The year (BS) whose first day is our Bikram Sambat Epoch (BSE) + * BS_YEAR_ZERO <- The year (BS) whose first day is our Bikram Sambat Epoch (BSE) * -622359900000 <- unix timestamp of BSE ('1950-4-13') */ private BikramSambatDate toBik(String greg) throws BsException { - int year = 2007; + int year = BS_YEAR_ZERO; int days; try { days = (int) Math.floor((parseDate(greg) - BS_EPOCH_TS) / MS_PER_DAY) + 1; diff --git a/js/package.json b/js/package.json index 2e5947a..23119ee 100644 --- a/js/package.json +++ b/js/package.json @@ -9,7 +9,7 @@ }, "repository": { "type": "git", - "url": "git+ssh://git@github.com/alxndrsn/bikram-sambat.js.git" + "url": "git+ssh://git@github.com/medic/bikram-sambat.git" }, "keywords": [ "bikram", @@ -22,9 +22,9 @@ "author": "alxndrsn", "license": "Apache-2.0", "bugs": { - "url": "https://github.com/alxndrsn/bikram-sambat.js/issues" + "url": "https://github.com/medic/bikram-sambat/issues" }, - "homepage": "https://github.com/alxndrsn/bikram-sambat.js#readme", + "homepage": "https://github.com/medic/bikram-sambat#readme", "devDependencies": { "chai": "^3.5.0", "grunt": "^1.0.1", diff --git a/js/src/index.js b/js/src/index.js index e122577..645a40f 100644 --- a/js/src/index.js +++ b/js/src/index.js @@ -1,29 +1,29 @@ var toDevanagari = require('eurodigit/src/to_non_euro').devanagari; - var MS_PER_DAY = 86400000; +var MONTH_NAMES = ['बैशाख', 'जेठ', 'असार', 'साउन', 'भदौ', 'असोज', 'कार्तिक', 'मंसिर', 'पौष', 'माघ', 'फाल्गुन', 'चैत']; -// We have defined our own Epoch for Bikram Sambat: 1-1-2007 BS / 13-4-1950 AD -var BS_EPOCH_TS = -622359900000; // = Date.parse('1950-4-13') -var BS_YEAR_ZERO = 2007; +// ------ TO UPDATE THESE HARDCODED VALUES USE /scripts/encode-days-in-month.js +// We have defined our own Epoch for Bikram Sambat: 1970-1-1 BS or 1913-4-13 AD +var BS_EPOCH_TS = -1789990200000; // = Date.parse('1913-4-13') +var BS_YEAR_ZERO = 1970; +var ENCODED_MONTH_LENGTHS = [ + 5315258,5314490,9459438,8673005,5315258,5315066,9459438,8673005,5315258,5314298,9459438,5327594,5315258,5314298,9459438,5327594,5315258,5314286,9459438,5315306,5315258,5314286,8673006,5315306,5315258,5265134,8673006,5315258,5315258,9459438,8673005,5315258,5314298,9459438,8673005,5315258,5314298,9459438,8473322,5315258,5314298,9459438,5327594,5315258,5314298,9459438,5327594,5315258,5314286,8673006,5315306,5315258,5265134,8673006,5315306,5315258,9459438,8673005,5315258,5314490,9459438,8673005,5315258,5314298,9459438,8473325,5315258,5314298,9459438,5327594,5315258,5314298,9459438,5327594,5315258,5314286,9459438,5315306,5315258,5265134,8673006,5315306,5315258,5265134,8673006,5315258,5314490,9459438,8673005,5315258,5314298,9459438,8669933,5315258,5314298,9459438,8473322,5315258,5314298,9459438,5327594,5315258,5314286,9459438,5315306,5315258,5265134,8673006,5315306,5315258,5265134,5527290,5527277,5527226,5527226,5528046,5527277,5528250,5528057,5527277,5527277 +]; -// TODO this would be stored more efficiently converted to a string using +// TODO ENCODED_MONTH_LENGTHS would be stored more efficiently converted to a string using // String.fromCharCode.apply(String, ENCODED_MONTH_LENGTHS), and extracted using // ENC_MTH.charCodeAt(...). However, JS seems to do something weird with the // top bits. -var ENCODED_MONTH_LENGTHS = [ - 8673005,5315258,5314298,9459438,8673005,5315258,5314298,9459438,8473322,5315258,5314298,9459438,5327594,5315258,5314298,9459438,5327594,5315258,5314286,8673006,5315306,5315258,5265134,8673006,5315306,5315258,9459438,8673005,5315258,5314490,9459438,8673005,5315258,5314298,9459438,8473325,5315258,5314298,9459438,5327594,5315258,5314298,9459438,5327594,5315258,5314286,9459438,5315306,5315258,5265134,8673006,5315306,5315258,5265134,8673006,5315258,5314490,9459438,8673005,5315258,5314298,9459438,8669933,5315258,5314298,9459438,8473322,5315258,5314298,9459438,5327594,5315258,5314286,9459438,5315306,5315258,5265134,8673006,5315306,5315258,5265134,5527290,5527277,5527226,5527226,5528046,5527277,5528250,5528057,5527277,5527277 - ], - MONTH_NAMES = ['बैशाख', 'जेठ', 'असार', 'साउन', 'भदौ', 'असोज', 'कार्तिक', 'मंसिर', 'पौष', 'माघ', 'फाल्गुन', 'चैत']; /** * Magic numbers: - * 2000 <- the first year (BS) encoded in ENCODED_MONTH_LENGTHS + * BS_YEAR_ZERO <- the first year (BS) encoded in ENCODED_MONTH_LENGTHS * month #5 <- this is the only month which has a day variation of more than 1 * & 3 <- this is a 2 bit mask, i.e. 0...011 */ function daysInMonth(year, month) { if(month < 1 || month > 12) throw new Error('Invalid month value ' + month); - var delta = ENCODED_MONTH_LENGTHS[year - 2000]; + var delta = ENCODED_MONTH_LENGTHS[year - BS_YEAR_ZERO]; if(typeof delta === 'undefined') throw new Error('No data for year: ' + year + ' BS'); return 29 + ((delta >>> (((month-1) << 1))) & 3); @@ -68,12 +68,16 @@ function toGreg(year, month, day) { if(year < BS_YEAR_ZERO) throw new Error('Invalid year value ' + year); if(day < 1 || day > daysInMonth(year, month)) throw new Error('Invalid day value', day); - var timestamp = BS_EPOCH_TS; + var timestamp = BS_EPOCH_TS + (MS_PER_DAY * day); + month--; - while(year >= BS_YEAR_ZERO) { - do while(day--) timestamp += MS_PER_DAY; - while(--month && (day = daysInMonth(year, month))); - day = daysInMonth(--year, month = 12); + while (year >= BS_YEAR_ZERO) { + while (month > 0) { + timestamp += (MS_PER_DAY * daysInMonth(year, month)); + month--; + } + month = 12; + year--; } var d = new Date(timestamp); diff --git a/js/test/index.js b/js/test/index.js index 933cc4c..dc4bbc8 100644 --- a/js/test/index.js +++ b/js/test/index.js @@ -74,7 +74,7 @@ describe('bikram-sambat', function() { it('should throw Error if year is too small', () => { assert.throw(() => - bs.toGreg(2006, 1, 1)); + bs.toGreg(1969, 1, 1)); }); diff --git a/scripts/encode-days-in-month.js b/scripts/encode-days-in-month.js new file mode 100644 index 0000000..208707c --- /dev/null +++ b/scripts/encode-days-in-month.js @@ -0,0 +1,62 @@ +const daysInMonth = require('../test-data/daysInMonth.json'); + +/* +INSTRUCTIONS: + +1) Update `/test-data/daysInMonth.json` as required +2) The first entry in `/test-data/daysInMonth.json` as the BS Epoch. Take this + and convert it to AD and update the bsEpoch constant below. +3) Run the script: node ./encode-days-in-month.js +4) Copy the output code into the files overwriting the existing hardcoded values + +*/ + +// const bsEpoch = false; // eg: '1913-4-13'; +const bsEpoch = '1913-4-13'; + +if (!bsEpoch) { + console.error('Read the instructions at the top of this file before executing.'); + process.exit(1); +} + +const processMonth = decimal => { + const delta = decimal - 29; + const binary = delta.toString(2); + const padded = binary.length === 1 ? '0' + binary : binary; + return padded; +}; + +const processYear = months => { + const binary = months.map(processMonth); + const combined = binary.reverse().join(''); + const decimal = parseInt(combined, 2); + return decimal; +}; + +const encodedLengths = Object.values(daysInMonth).map(processYear); +const yearZero = Object.keys(daysInMonth)[0]; +const bsEpochTs = Date.parse(bsEpoch); + +console.log(` +// JavaScript - copy into js/src/index.js + +// ------ TO UPDATE THESE HARDCODED VALUES USE /scripts/encode-days-in-month.js +// We have defined our own Epoch for Bikram Sambat: ${yearZero}-1-1 BS or ${bsEpoch} AD +var BS_EPOCH_TS = ${bsEpochTs}; // = Date.parse('${bsEpoch}') +var BS_YEAR_ZERO = ${yearZero}; +var ENCODED_MONTH_LENGTHS = [ + ${encodedLengths.join(',')} +]; +`); + +console.log(` +// Java - copy into java/lib/src/main/java/bikramsambat/BsCalendar.java + +// ------ TO UPDATE THESE HARDCODED VALUES USE /scripts/encode-days-in-month.js +// We have defined our own Epoch for Bikram Sambat: ${yearZero}-1-1 BS or ${bsEpoch} AD +private static final long BS_EPOCH_TS = ${bsEpochTs}L; // ${bsEpoch} AD +private static final int BS_YEAR_ZERO = ${yearZero}; +private static final long[] ENCODED_MONTH_LENGTHS = { + ${encodedLengths.map(length => length + 'L').join(',')} +}; +`); diff --git a/test-data/daysInMonth.json b/test-data/daysInMonth.json index a3af4d7..ce48226 100644 --- a/test-data/daysInMonth.json +++ b/test-data/daysInMonth.json @@ -1,4 +1,34 @@ { + "1970": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1971": [31, 31, 32, 31, 32, 30, 30, 29, 30, 29, 30, 30], + "1972": [31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], + "1973": [30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], + "1974": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1975": [31, 31, 32, 32, 30, 31, 30, 29, 30, 29, 30, 30], + "1976": [31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], + "1977": [30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], + "1978": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1979": [31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], + "1980": [31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], + "1981": [31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], + "1982": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1983": [31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], + "1984": [31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], + "1985": [31, 31, 31, 32, 31, 31, 29, 30, 30, 29, 30, 30], + "1986": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1987": [31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30], + "1988": [31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], + "1989": [31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], + "1990": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1991": [31, 32, 31, 32, 31, 30, 30, 29, 30, 29, 30, 30], + "1992": [31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], + "1993": [31, 31, 31, 32, 31, 31, 30, 29, 30, 29, 30, 30], + "1994": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1995": [31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 30], + "1996": [31, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], + "1997": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1998": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], + "1999": [31, 32, 31, 32, 31, 30, 30, 30, 29, 29, 30, 31], "2000": [30, 32, 31, 32, 31, 30, 30, 30, 29, 30, 29, 31], "2001": [31, 31, 32, 31, 31, 31, 30, 29, 30, 29, 30, 30], "2002": [31, 31, 32, 32, 31, 30, 30, 29, 30, 29, 30, 30], diff --git a/test-data/toBik_euro.json b/test-data/toBik_euro.json index d398838..f50c3aa 100644 --- a/test-data/toBik_euro.json +++ b/test-data/toBik_euro.json @@ -1,4 +1,5 @@ { + "1913-04-13": "1970-01-01", "1950-04-13": "2007-01-01", "1950-04-14": "2007-01-02", "1950-04-15": "2007-01-03", diff --git a/test-data/toGreg.json b/test-data/toGreg.json index 5eafec7..f46d04d 100644 --- a/test-data/toGreg.json +++ b/test-data/toGreg.json @@ -1,4 +1,16 @@ [ + { + "bs": [ + 1970, + 1, + 1 + ], + "expectedGreg": [ + 1913, + 4, + 13 + ] + }, { "bs": [ 2007,