UNPKG

coffeescript-ui

Version:
553 lines (485 loc) 20.5 kB
class CUI.DateTimeRangeGrammar @REGEXP_DATE = /^[0-9]+[-/.][0-9]+[-/.][0-9]+$/ # Exact date @REGEXP_MONTH = /^[0-9]+[-/.][0-9]+$/ # Matches if the date has Year and month (no day) @REGEXP_YEAR = /^\-?[0-9]+$/ # Matches if the date has Year. (no day, no month) @REGEXP_YEAR_DOT = /^[0-9]+.$/ # Matches Year ending with dot. @REGEXP_CENTURY = /^[0-9]+th$/ # Matches Year ending with th. @REGEXP_SPACE = /\s+/ @TYPE_DATE = "DATE" @TYPE_YEAR = "YEAR" @TYPE_YEAR_DOT = "YEAR_DOT" @TYPE_CENTURY = "CENTURY" @DASH = "-" @STORE_FORMAT = "store" @OUTPUT_YEAR = "year" @OUTPUT_DATE = "date" @DISPLAY_ATTRIBUTE_YEAR_MONTH = "year-month" ### How to add new grammars: # [<GRAMMAR>, <FUNCTION>, <ARRAY_TOKEN_POSITION>, <ARRAY_FUNCTION_ARGUMENTS>, <KEY>] # # GRAMMAR: Plain text with tokens to identify dates. # - Tokens available: DATE (full date), YEAR (only year), YEAR_DOT (year with dot), CENTURY (year with th) # # FUNCTION: Function which is going to be used to parse, in string. # - Functions available: millennium, century, earlyCentury, lateCentury, range, yearRange, getFromTo # # ARRAY_TOKEN_POSITION: Array of integers with the position of the date tokens. # ARRAY_FUNCTION_ARGUMENTS: (optional) Array with arguments that will be given to the function. # - Dates are given as arguments automatically. # # KEY: (optional) Identifier that is used in the format method. ### @PARSE_GRAMMARS = {} # TODO: Add ES & IT. @PARSE_GRAMMARS["de-DE"] = [ ["DATE bis DATE", "range", [0, 2], null, "RANGE"] ["YEAR bis YEAR", "range", [0, 2]] ["YEAR - YEAR n. Chr.", "range", [0, 2]] ["YEAR bis YEAR n. Chr.", "range", [0, 2]] ["zwischen YEAR bis YEAR", "range", [1, 3]] ["zwischen YEAR und YEAR", "range", [1, 3]] ["zwischen DATE bis DATE", "range", [1, 3]] ["zwischen DATE und DATE", "range", [1, 3]] ["von YEAR bis YEAR", "range", [1, 3]] ["YEAR v. Chr. - YEAR n. Chr.", "range", [0, 4], [true]] ["YEAR - YEAR v. Chr.", "range", [0, 2], [true, true]] ["YEAR BC - YEAR n. Chr.", "range", [0, 3], [true]] ["YEAR BC - YEAR nach Chr.", "range", [0, 3], [true]] ["YEAR B.C. - YEAR n. Chr.", "range", [0, 3], [true]] ["YEAR B.C. - YEAR nach Chr.", "range", [0, 3], [true]] ["YEAR bc - YEAR n. Chr.", "range", [0, 3], [true]] ["YEAR bc - YEAR nach Chr.", "range", [0, 3], [true]] ["YEAR ac - YEAR n. Chr.", "range", [0, 3], [true]] ["YEAR ac - YEAR nach Chr.", "range", [0, 3], [true]] ["bis YEAR", "yearRange", [1], [false, true]] ["um YEAR", "yearRange", [1], [false, true, true], "AROUND"] ["ca. YEAR", "yearRange", [1], [false, true, true]] ["um YEAR bc", "yearRange", [1], [true, true, true]] ["um YEAR v. Chr.", "yearRange", [1], [true, true, true], "AROUND_BC"] ["ca. YEAR bc", "yearRange", [1], [true, true, true]] ["ca. YEAR v. Chr.", "yearRange", [1], [true, true, true]] ["vor YEAR", "yearRange", [1], [false, true], "BEFORE"] ["vor YEAR bc", "yearRange", [1], [true, true]] ["vor YEAR v. Chr.", "yearRange", [1], [true, true], "BEFORE_BC"] ["nach YEAR", "yearRange", [1], [false, false, true], "AFTER"] ["nach YEAR ad", "yearRange", [1], [false, false, true]] ["nach YEAR A.D.", "yearRange", [1], [false, false, true]] ["ab YEAR", "yearRange", [1], [false, false, true]] ["ab YEAR ad", "yearRange", [1], [false, false, true]] ["ab YEAR A.D.", "yearRange", [1], [false, false, true]] ["nach YEAR bc", "yearRange", [1], [true, false, true]] ["nach YEAR v. Chr.", "yearRange", [1], [true, false, true], "AFTER_BC"] ["YEAR v. Chr.", "getFromTo", [0], [true]] ["YEAR n. Chr.", "getFromTo", [0]] ["YEAR_DOT Jhd. vor Chr", "century", [0], [true]] ["YEAR_DOT Jhd. vor Chr.", "century", [0], [true]] ["YEAR_DOT Jhd. v. Chr", "century", [0], [true]] ["YEAR_DOT Jhd. v. Chr.", "century", [0], [true], "CENTURY_BC"] ["YEAR_DOT Jhd vor Chr", "century", [0], [true]] ["YEAR_DOT Jhd vor Chr.", "century", [0], [true]] ["YEAR_DOT Jhd v. Chr", "century", [0], [true]] ["YEAR_DOT Jhd v. Chr.", "century", [0], [true]] ["YEAR_DOT Jh vor Chr", "century", [0], [true]] ["YEAR_DOT Jh vor Chr.", "century", [0], [true]] ["YEAR_DOT Jh v. Chr", "century", [0], [true]] ["YEAR_DOT Jh v. Chr.", "century", [0], [true]] ["YEAR_DOT Jh. vor Chr", "century", [0], [true]] ["YEAR_DOT Jh. vor Chr.", "century", [0], [true]] ["YEAR_DOT Jh. v. Chr", "century", [0], [true]] ["YEAR_DOT Jh. v. Chr.", "century", [0], [true]] ["YEAR Jhd. vor Chr", "century", [0], [true]] ["YEAR Jhd. vor Chr.", "century", [0], [true]] ["YEAR Jhd. v. Chr", "century", [0], [true]] ["YEAR Jhd. v. Chr.", "century", [0], [true]] ["YEAR Jhd vor Chr", "century", [0], [true]] ["YEAR Jhd vor Chr.", "century", [0], [true]] ["YEAR Jhd v. Chr", "century", [0], [true]] ["YEAR Jhd v. Chr.", "century", [0], [true]] ["YEAR Jhd ac", "century", [0], [true]] ["YEAR Jh ac", "century", [0], [true]] ["YEAR Jh. ac", "century", [0], [true]] ["YEAR Jhd.", "century", [0]] ["YEAR Jhd", "century", [0]] ["YEAR Jh", "century", [0]] ["YEAR Jh.", "century", [0]] ["YEAR_DOT Jhd.", "century", [0], null, "CENTURY"] ["YEAR_DOT Jhd", "century", [0]] ["YEAR_DOT Jhd nach Chr.", "century", [0]] ["YEAR_DOT Jhd. nach Chr.", "century", [0]] ["YEAR_DOT Jhd. nach Chr", "century", [0]] ["YEAR_DOT Jhd nach Chr", "century", [0]] ["YEAR_DOT Jhd. n. Chr.", "century", [0]] ["YEAR_DOT Jhd. n. Chr", "century", [0]] ["YEAR_DOT Jhd n. Chr.", "century", [0]] ["YEAR_DOT Jhd n. Chr", "century", [0]] ["YEAR_DOT Jt. v. Chr.", "millennium", [0], [true], "MILLENNIUM_BC"] ["YEAR_DOT Jt v. Chr.", "millennium", [0], [true]] ["YEAR_DOT Jt bc", "millennium", [0], [true]] ["YEAR_DOT Jt. bc", "millennium", [0], [true]] ["YEAR_DOT Jt BC", "millennium", [0], [true]] ["YEAR_DOT Jt. BC", "millennium", [0], [true]] ["YEAR_DOT Jt.", "millennium", [0], null, "MILLENNIUM"] ["YEAR_DOT Jt.", "millennium", [0]] ["YEAR_DOT Jt", "millennium", [0]] ["YEAR Jt. v. Chr.", "millennium", [0], [true]] ["YEAR Jt v. Chr.", "millennium", [0], [true]] ["YEAR Jt bc", "millennium", [0], [true]] ["YEAR Jt. bc", "millennium", [0], [true]] ["YEAR Jt BC", "millennium", [0], [true]] ["YEAR Jt. BC", "millennium", [0], [true]] ["YEAR Jt.", "millennium", [0]] ["YEAR Jt.", "millennium", [0]] ["YEAR Jt", "millennium", [0]] ["Anfang YEAR_DOT Jhd", "earlyCentury", [1]] ["Anfang YEAR_DOT Jh", "earlyCentury", [1]] ["Anfang YEAR_DOT Jh.", "earlyCentury", [1], null, "EARLY_CENTURY"] ["Anfang YEAR_DOT Jhd v. Chr.", "earlyCentury", [1], [true]] ["Anfang YEAR_DOT Jh v. Chr.", "earlyCentury", [1], [true]] ["Anfang YEAR_DOT Jh. v. Chr.", "earlyCentury", [1], [true], "EARLY_CENTURY_BC"] ["Ende YEAR_DOT Jhd", "lateCentury", [1]] ["Ende YEAR_DOT Jh", "lateCentury", [1]] ["Ende YEAR_DOT Jh.", "lateCentury", [1], null, "LATE_CENTURY"] ["Ende YEAR_DOT Jhd v. Chr.", "lateCentury", [1], [true]] ["Ende YEAR_DOT Jh v. Chr.", "lateCentury", [1], [true]] ["Ende YEAR_DOT Jh. v. Chr.", "lateCentury", [1], [true], "LATE_CENTURY_BC"] ] @PARSE_GRAMMARS["en-US"] = [ ["DATE to DATE", "range", [0, 2], null, "RANGE"] ["YEAR to YEAR", "range", [0, 2]] ["YEAR - YEAR A.D.", "range", [0, 2]] ["YEAR - YEAR AD", "range", [0, 2]] ["YEAR to YEAR A.D.", "range", [0, 2]] ["YEAR to YEAR AD", "range", [0, 2]] ["from YEAR to YEAR", "range", [1, 3]] ["YEAR BC - YEAR A.D.", "range", [0, 3], [true]] ["YEAR BC - YEAR AD", "range", [0, 3], [true]] ["YEAR BC - YEAR CE", "range", [0, 3], [true]] ["YEAR BC - YEAR ad", "range", [0, 3], [true]] ["YEAR B.C. - YEAR A.D.", "range", [0, 3], [true]] ["YEAR B.C. - YEAR AD", "range", [0, 3], [true]] ["YEAR B.C. - YEAR CE", "range", [0, 3], [true]] ["YEAR B.C. - YEAR ad", "range", [0, 3], [true]] ["YEAR B.C. to YEAR", "range", [0, 3], [true]] ["YEAR bc - YEAR A.D.", "range", [0, 3], [true]] ["YEAR bc - YEAR AD", "range", [0, 3], [true]] ["YEAR bc - YEAR CE", "range", [0, 3], [true]] ["YEAR bc - YEAR ad", "range", [0, 3], [true]] ["YEAR ac - YEAR A.D.", "range", [0, 3], [true]] ["YEAR ac - YEAR AD", "range", [0, 3], [true]] ["YEAR ac - YEAR CE", "range", [0, 3], [true]] ["YEAR ac - YEAR ad", "range", [0, 3], [true]] ["YEAR - YEAR BC", "range", [0, 2], [true, true]] ["YEAR - YEAR B.C.", "range", [0, 2], [true, true]] ["after YEAR", "yearRange", [1], [false, false, true], "AFTER"] ["after YEAR BC", "yearRange", [1], [true, false, true], "AFTER_BC"] ["before YEAR", "yearRange", [1], [false, true], "BEFORE"] ["before YEAR BC", "yearRange", [1], [true, true], "BEFORE_BC"] ["around YEAR", "yearRange", [1], [false, true, true], "AROUND"] ["around YEAR BC", "yearRange", [1], [true, true, true], "AROUND_BC"] ["YEAR_DOT millennium", "millennium", [0], null, "MILLENNIUM"] ["YEAR_DOT millennium BC", "millennium", [0], [true], "MILLENNIUM_BC"] ["YEAR millennium", "millennium", [0]] ["YEAR millennium BC", "millennium", [0], [true]] ["YEAR BCE", "getFromTo", [0], [true]] ["YEAR bc", "getFromTo", [0], [true]] ["YEAR BC", "getFromTo", [0], [true]] ["YEAR B.C.", "getFromTo", [0], [true]] ["YEAR AD", "getFromTo", [0]] ["YEAR A.D.", "getFromTo", [0]] ["CENTURY century", "century", [0], null, "CENTURY"] ["CENTURY century BC", "century", [0], [true], "CENTURY_BC"] ["Early CENTURY century", "earlyCentury", [1], null, "EARLY_CENTURY"] ["Early CENTURY century BC", "earlyCentury", [1], [true], "EARLY_CENTURY_BC"] ["Late CENTURY century", "lateCentury", [1], null, "LATE_CENTURY"] ["Late CENTURY century BC", "lateCentury", [1], [true], "LATE_CENTURY_BC"] ] @dateRangeToString: (from, to) -> if not CUI.util.isString(from) or not CUI.util.isString(to) return locale = CUI.DateTime.getLocale() fromMoment = CUI.DateTimeRangeGrammar.getMoment(from) toMoment = CUI.DateTimeRangeGrammar.getMoment(to) if not fromMoment?.isValid() or not toMoment?.isValid() return CUI.DateTimeFormats[locale].formats[0].invalid if CUI.DateTimeRangeGrammar.REGEXP_YEAR.test(from) fromIsYear = true fromYear = parseInt(from) else if fromMoment.isValid() and fromMoment.date() == 1 and fromMoment.month() == 0 # First day of year. fromYear = fromMoment.year() if from == to return CUI.DateTime.format(from, "display_short") if CUI.DateTimeRangeGrammar.REGEXP_YEAR.test(to) toIsYear = true toYear = parseInt(to) else if toMoment.isValid() and toMoment.date() == 31 and toMoment.month() == 11 # Last day of year toYear = toMoment.year() grammars = CUI.DateTimeRangeGrammar.PARSE_GRAMMARS[locale] getPossibleString = (key, parameters) -> for _grammar in grammars if _grammar[4] == key grammar = _grammar break if not grammar return possibleStringArray = grammar[0].split(CUI.DateTimeRangeGrammar.REGEXP_SPACE) tokenPositions = grammar[2] for value, index in tokenPositions if parameters[index] if possibleStringArray[value] == "YEAR_DOT" parameters[index] += "." else if possibleStringArray[value] == "CENTURY" parameters[index] += "th" else if CUI.util.isString(parameters[index]) parameters[index] = CUI.DateTime.format(parameters[index], "display_short") possibleStringArray[value] = parameters[index] possibleString = possibleStringArray.join(" ") output = CUI.DateTimeRangeGrammar.stringToDateRange(possibleString) if not output or output.to != to or output.from != from return return possibleString if not CUI.util.isUndef(fromYear) and not CUI.util.isUndef(toYear) if fromYear == toYear return "#{fromYear}" isBC = fromYear <= 0 if isBC possibleString = getPossibleString("AFTER_BC", [Math.abs(fromYear)]) else possibleString = getPossibleString("AFTER", [Math.abs(fromYear)]) if possibleString return possibleString isBC = toYear <= 0 if isBC possibleString = getPossibleString("BEFORE_BC", [Math.abs(toYear)]) else possibleString = getPossibleString("BEFORE", [Math.abs(toYear)]) if possibleString return possibleString centerYear = (toYear + fromYear) / 2 isBC = centerYear <= 0 if isBC possibleString = getPossibleString("AROUND_BC", [Math.abs(centerYear)]) else possibleString = getPossibleString("AROUND", [Math.abs(centerYear)]) if possibleString return possibleString yearsDifference = toYear - fromYear if yearsDifference == 999 # Millennium isBC = toYear <= 0 if isBC millennium = (-from + 1) / 1000 possibleString = getPossibleString("MILLENNIUM_BC", [millennium]) else millennium = to / 1000 possibleString = getPossibleString("MILLENNIUM", [millennium]) if possibleString return possibleString else if yearsDifference == 99 # Century isBC = toYear <= 0 if isBC century = -(fromYear - 1) / 100 possibleString = getPossibleString("CENTURY_BC", [century]) else century = toYear / 100 possibleString = getPossibleString("CENTURY", [century]) if possibleString return possibleString else if yearsDifference == 15 # Early/Late isBC = fromYear <= 0 century = Math.ceil(Math.abs(fromYear) / 100) if isBC for key in ["EARLY_CENTURY_BC", "LATE_CENTURY_BC"] possibleString = getPossibleString(key, [century]) if possibleString return possibleString else for key in ["EARLY_CENTURY", "LATE_CENTURY"] possibleString = getPossibleString(key, [century]) if possibleString return possibleString if fromMoment.year() == toMoment.year() and fromMoment.month() == toMoment.month() and fromMoment.date() == 1 and toMoment.date() == toMoment.endOf("month").date() for format in CUI.DateTimeFormats[locale].formats if format.display_attribute == CUI.DateTimeRangeGrammar.DISPLAY_ATTRIBUTE_YEAR_MONTH return toMoment.format(format.display_short) if fromIsYear or toIsYear if fromIsYear from = CUI.DateTime.format(from, "display_short") if toIsYear to = CUI.DateTime.format(to, "display_short") # Removes the 'BC' / v. Chr. from 'from' to only show it in the end. For example: 15 - 10 v. Chr. fromSplit = from.split(CUI.DateTimeRangeGrammar.REGEXP_SPACE) toSplit = to.split(CUI.DateTimeRangeGrammar.REGEXP_SPACE) if fromSplit[1] == toSplit[1] from = fromSplit[0] return "#{from} - #{to}" possibleString = getPossibleString("RANGE", [from, to]) if possibleString return possibleString return "#{from} - #{to}" # Main method to check against every grammar. @stringToDateRange: (input) -> if CUI.util.isEmpty(input) or not CUI.util.isString(input) return error: "Input needs to be a non empty string: #{input}" input = input.trim() tokens = [] for s in input.split(CUI.DateTimeRangeGrammar.REGEXP_SPACE) value = s if CUI.DateTimeRangeGrammar.REGEXP_DATE.test(s) or CUI.DateTimeRangeGrammar.REGEXP_MONTH.test(s) type = CUI.DateTimeRangeGrammar.TYPE_DATE else if CUI.DateTimeRangeGrammar.REGEXP_YEAR.test(s) type = CUI.DateTimeRangeGrammar.TYPE_YEAR else if CUI.DateTimeRangeGrammar.REGEXP_YEAR_DOT.test(s) type = CUI.DateTimeRangeGrammar.TYPE_YEAR_DOT value = s.split(".")[0] else if CUI.DateTimeRangeGrammar.REGEXP_CENTURY.test(s) type = CUI.DateTimeRangeGrammar.TYPE_CENTURY value = s.split("th")[0] else type = s # The type for everything else is the value. tokens.push type: type value: value stringToParse = tokens.map((token) -> token.type).join(" ").toUpperCase() # Check if there is a grammar that applies to the input. for _, grammars of CUI.DateTimeRangeGrammar.PARSE_GRAMMARS for grammar in grammars if grammar[0].toUpperCase() == stringToParse method = CUI.DateTimeRangeGrammar[grammar[1]] if not CUI.util.isFunction(method) or not grammar[2] continue tokenPositions = grammar[2] if CUI.util.isArray(tokenPositions) if tokenPositions.length == 0 continue methodArguments = tokenPositions.map((index) -> tokens[index].value) else methodArguments = [tokenPositions] if extraArguments = grammar[3] if CUI.util.isArray(extraArguments) methodArguments = methodArguments.concat(extraArguments) else methodArguments.push(extraArguments) value = method.apply(@, methodArguments) if value return value # If there is no grammar available, we try to parse the date. [from, to] = input.split(/\s+\-\s+/) if from and to output = CUI.DateTimeRangeGrammar.range(from, to) if output return output output = CUI.DateTimeRangeGrammar.getFromTo(from) if output return output return error: "NoDateRangeFound #{input}" @millennium: (millennium, isBC) -> if isBC from = -(millennium * 1000 - 1) to = -((millennium - 1) * 1000) else to = millennium * 1000 from = to - 999 return CUI.DateTimeRangeGrammar.getFromToWithRange("#{from}", "#{to}") @century: (century, isBC) -> century = century * 100 if isBC to = -(century - 100) from = -century + 1 else to = century from = century - 99 return CUI.DateTimeRangeGrammar.getFromToWithRange("#{from}", "#{to}") # 15 -> 1401 - 1416 # 15 -> 1499 1484 @earlyCentury: (century, isBC) -> century = century * 100 if isBC from = -century + 1 to = from + 15 else from = century - 99 to = from + 15 return CUI.DateTimeRangeGrammar.getFromToWithRange("#{from}", "#{to}") # 15 - -1416 -1401 # 15 - 1484 - 1499 @lateCentury: (century, isBC) -> century = century * 100 if isBC to = -(century - 101) from = to - 15 else to = century from = to - 15 return CUI.DateTimeRangeGrammar.getFromToWithRange("#{from}", "#{to}") @range: (from, to, fromBC = false, toBC = false) -> if toBC and not to.startsWith(CUI.DateTimeRangeGrammar.DASH) to = @__toBC(to) if (fromBC or toBC) and not from.startsWith(CUI.DateTimeRangeGrammar.DASH) from = @__toBC(from) return CUI.DateTimeRangeGrammar.getFromToWithRange(from, to) @yearRange: (year, isBC = false, fromAddYears = false, toAddYears = false) -> if isBC and not year.startsWith(CUI.DateTimeRangeGrammar.DASH) year = "-#{year}" momentInputFrom = CUI.DateTimeRangeGrammar.getMoment(year) if not momentInputFrom?.isValid() return if fromAddYears or toAddYears _year = momentInputFrom.year() if _year % 1000 == 0 yearsToAdd = 500 else if _year % 100 == 0 yearsToAdd = 50 else if _year % 50 == 0 yearsToAdd = 15 else if _year % 10 == 0 yearsToAdd = 5 else yearsToAdd = 2 momentInputTo = momentInputFrom.clone() if fromAddYears momentInputFrom.add(-yearsToAdd, "year") if toAddYears momentInputTo.add(yearsToAdd, "year") momentInputFrom.startOf("year") momentInputTo.endOf("year") from = CUI.DateTimeRangeGrammar.format(momentInputFrom) to = CUI.DateTimeRangeGrammar.format(momentInputTo) return from: from, to: to @getFromTo: (inputString, isBC = false) -> if isBC and not inputString.startsWith(CUI.DateTimeRangeGrammar.DASH) inputString = @__toBC(inputString) momentInput = CUI.DateTimeRangeGrammar.getMoment(inputString) if not momentInput?.isValid() return if inputString.match(CUI.DateTimeRangeGrammar.REGEXP_YEAR) from = to = CUI.DateTimeRangeGrammar.format(momentInput) else if inputString.match(CUI.DateTimeRangeGrammar.REGEXP_MONTH) from = CUI.DateTimeRangeGrammar.format(momentInput, false) momentInput.endOf('month'); to = CUI.DateTimeRangeGrammar.format(momentInput, false) else from = to = CUI.DateTimeRangeGrammar.format(momentInput, false) return from: from, to: to @getFromToWithRange: (fromDate, toDate) -> from = CUI.DateTimeRangeGrammar.getFromTo(fromDate) to = CUI.DateTimeRangeGrammar.getFromTo(toDate) if not from or not to return return from: from.from, to: to.to @format: (dateMoment, yearFormat = true) -> if yearFormat outputType = CUI.DateTimeRangeGrammar.OUTPUT_YEAR else outputType = CUI.DateTimeRangeGrammar.OUTPUT_DATE return CUI.DateTime.format(dateMoment, CUI.DateTimeRangeGrammar.STORE_FORMAT, outputType) @getMoment: (inputString) -> if not CUI.util.isString(inputString) return dateTime = new CUI.DateTime() momentInput = dateTime.parseValue(inputString); dateTime.destroy() return momentInput @__toBC: (inputString) -> if inputString.match(CUI.DateTimeRangeGrammar.REGEXP_YEAR) inputString = parseInt(inputString) - 1 return "-#{inputString}"