Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for a custom period formatter #8

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ package grails.plugin.jodatime.taglib

import org.joda.time.Duration
import org.joda.time.DurationFieldType
import org.joda.time.Period
import org.joda.time.PeriodType
import org.joda.time.format.PeriodFormat
import static org.joda.time.DurationFieldType.months
import static org.joda.time.DurationFieldType.years
import grails.plugin.jodatime.util.PeriodUtils

class PeriodTagLib {

Expand All @@ -32,7 +30,7 @@ class PeriodTagLib {
def id = attrs.id ?: name
def value = attrs.value

def periodType = getPeriodType(attrs.fields, DEFAULT_PERIOD_TYPE)
def periodType = PeriodUtils.getPeriodType(attrs.fields, DEFAULT_PERIOD_TYPE)

if (value instanceof Duration) {
value = value.toPeriod(periodType)
Expand All @@ -50,63 +48,19 @@ class PeriodTagLib {
}

def formatPeriod = {attrs ->
def value = attrs.value
if (!value) {
if (!attrs.value) {
throwTagError("'value' attribute is required")
}

def periodType = getPeriodType(attrs.fields, PeriodType.standard())

if (value instanceof Duration) {
value = value.toPeriod(periodType)
} else {
value = safeNormalize(value, periodType)
}

def formatter = PeriodFormat.wordBased(request.locale)

out << formatter.print(value)
}

private PeriodType getPeriodType(String fields, PeriodType defaultPeriodType) {
PeriodType periodType
if (fields) {
periodType = getPeriodTypeForFields(fields)
} else if (grailsApplication.config.jodatime?.periodpicker?.default?.fields) {
periodType = getPeriodTypeForFields(grailsApplication.config.jodatime.periodpicker.default.fields)
} else {
periodType = defaultPeriodType
}
return periodType
}

private static final PeriodType DEFAULT_PERIOD_TYPE = getPeriodTypeForFields("hours,minutes,seconds")

private static PeriodType getPeriodTypeForFields(String fields) {
def fieldTypes = fields.split(/\s*,\s*/).collect { DurationFieldType."$it"() } as DurationFieldType[]
return PeriodType.forFields(fieldTypes)
out << PeriodUtils.formatPeriod(attrs.value, attrs.fields, request.locale)
}

/**
* PeriodFormat.print will throw UnsupportedOperationException if years or months are present in period but
* not supported by the formatter so we trim those fields off to avoid the problem.
*/
private Period safeNormalize(Period value, PeriodType periodType) {
if (!periodType.isSupported(years()) && years() in value.getFieldTypes()) {
log.warn "Omitting years from value '$value' as format '$periodType' does not support years"
value = value.withYears(0)
}
if (!periodType.isSupported(months()) && months() in value.getFieldTypes()) {
log.warn "Omitting months from value '$value' as format '$periodType' does not support months"
value = value.withMonths(0)
}
return value.normalizedStandard(periodType)
}
private static final PeriodType DEFAULT_PERIOD_TYPE = PeriodUtils.getPeriodTypeForFields("hours,minutes,seconds")

private String getLabelFor(DurationFieldType fieldType) {
def bundle = ResourceBundle.getBundle("${PeriodFormat.package.name}.messages", request.locale)
def defaultLabel = bundle.getString("PeriodFormat.$fieldType.name").trim()
message(code: "${DurationFieldType.name}.$fieldType.name", default: defaultLabel)
}

}
}
92 changes: 92 additions & 0 deletions src/groovy/grails/plugin/jodatime/util/PeriodUtils.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2010 Rob Fletcher
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package grails.plugin.jodatime.util

import grails.util.Holders
import org.apache.log4j.Logger
import org.joda.time.Duration
import org.joda.time.DurationFieldType
import org.joda.time.Period
import org.joda.time.PeriodType
import org.joda.time.format.PeriodFormat
import org.joda.time.format.PeriodFormatter
import static org.joda.time.DurationFieldType.months
import static org.joda.time.DurationFieldType.years

/**
* Helper methods for Periods.
*/
class PeriodUtils {

private static final Logger log = Logger.getLogger(getClass())

/**
* Format a Duration or Period for display
* @param value the Period or Duration to format
* @param fields a comma separated list of fields to display
* @param locale the locale for the default formatter
* @param formatter an optional formatter to use in place of the default word based formatter
* @return the formatted string
*/
static formatPeriod(value, String fields, Locale locale, PeriodFormatter formatter=null) {
def periodType = getPeriodType(fields, PeriodType.standard())

if (value instanceof Duration) {
value = value.toPeriod(periodType)
} else {
value = safeNormalize(value, periodType)
}

if (!formatter) {
formatter = PeriodFormat.wordBased(locale)
}

return formatter.print(value)
}

static PeriodType getPeriodType(String fields, PeriodType defaultPeriodType) {
PeriodType periodType
if (fields) {
periodType = getPeriodTypeForFields(fields)
} else if (Holders.config.jodatime?.periodpicker?.default?.fields) {
periodType = getPeriodTypeForFields(Holders.config.jodatime.periodpicker.default.fields)
} else {
periodType = defaultPeriodType
}
return periodType
}

private static PeriodType getPeriodTypeForFields(String fields) {
def fieldTypes = fields.split(/\s*,\s*/).collect { DurationFieldType."$it"() } as DurationFieldType[]
return PeriodType.forFields(fieldTypes)
}

/**
* PeriodFormat.print will throw UnsupportedOperationException if years or months are present in period but
* not supported by the formatter so we trim those fields off to avoid the problem.
*/
private static Period safeNormalize(Period value, PeriodType periodType) {
if (!periodType.isSupported(years()) && years() in value.getFieldTypes()) {
log.warn "Omitting years from value '$value' as format '$periodType' does not support years"
value = value.withYears(0)
}
if (!periodType.isSupported(months()) && months() in value.getFieldTypes()) {
log.warn "Omitting months from value '$value' as format '$periodType' does not support months"
value = value.withMonths(0)
}
return value.normalizedStandard(periodType)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package grails.plugin.jodatime.taglib
import grails.test.mixin.TestFor
import org.codehaus.groovy.grails.web.taglib.exceptions.GrailsTagException
import org.joda.time.Period
import org.joda.time.format.PeriodFormatterBuilder
import spock.lang.Issue
import spock.lang.Specification
import spock.lang.Unroll
Expand Down Expand Up @@ -264,4 +265,4 @@ class PeriodTagLibSpec extends Specification {
value = new Period().withHours(1)
}

}
}
38 changes: 38 additions & 0 deletions test/unit/grails/plugin/jodatime/util/PeriodUtilsSpec.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2014 Donald Oellerich
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package grails.plugin.jodatime.util

import spock.lang.Specification
import org.joda.time.Period
import org.joda.time.format.PeriodFormatterBuilder
import static java.util.Locale.ENGLISH

class PeriodUtilsSpec extends Specification {

void 'formatPeriod accepts formatter attribute'() {
given:
def value = new Period().withWeeks(2).withHours(50).withMinutes(2).withSeconds(2)
def formatter = new PeriodFormatterBuilder()
.appendDays().appendSuffix("d").appendSeparator(", ")
.appendHours().appendSuffix("h").appendSeparator(" and ")
.appendMinutes().appendSuffix("m")
.toFormatter()

expect:
PeriodUtils.formatPeriod(value, "days,hours,minutes", ENGLISH, formatter) == "16d, 2h and 2m"
}
}