# Working with Periods

Period values represent amounts of time, such as `3h`, `2d`, or `1w3d4h`. In workflows, you usually work with period values when you read or update fields like Estimation and Spent time, add work items, or calculate dates from a duration.

A field that stores the `period` type returns a period object. To create a period object in workflow code, use the `date-time` module. To compare period values, convert them to a common unit first.

## Period String Syntax

The `date-time` module accepts period strings with numeric values followed by unit abbreviations. Specify units in descending order, without spaces.

| Unit | Description | Example |
| --- | --- | --- |
| `w` | Weeks | `2w` |
| `d` | Days | `4d` |
| `h` | Hours | `6h` |
| `m` | Minutes | `30m` |

For example, `2w4d3h15m` represents two weeks, four days, three hours, and fifteen minutes.

The way period values are displayed in YouTrack depends on the period format and time tracking settings for the instance. The compact string format is the format you use when you create period values in workflow code.

## Create Period Values

Use `dateTime.toPeriod()` to convert a period string or a number of milliseconds to a period object.

```JAVASCRIPT
const dateTime = require('@jetbrains/youtrack-scripting-api/date-time');

const threeHours = dateTime.toPeriod('3h');
const fullPeriod = dateTime.toPeriod('2w4d3h15m');
const fromMilliseconds = dateTime.toPeriod(3 * 60 * 60 * 1000);
```

When you write a value to a period custom field, assign a period object, not a plain string.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');
const dateTime = require('@jetbrains/youtrack-scripting-api/date-time');

exports.rule = entities.Issue.onChange({
    title: 'Set default estimation',
    guard: (ctx) => {
        return ctx.issue.isReported && !ctx.issue.fields.Estimation;
    },
    action: (ctx) => {
        ctx.issue.fields.Estimation = dateTime.toPeriod('1d4h');
    },
    requirements: {
        Estimation: {
            type: entities.Field.periodType
        }
    }
});
```

## Read Period Fields

Read a period field from `issue.fields`. Optional fields can be empty, so check that the value exists before you use it.

```JAVASCRIPT
const estimation = ctx.issue.fields.Estimation;

if (estimation) {
    const weeks = estimation.getWeeks();
    const days = estimation.getDays();
    const hours = estimation.getHours();
    const minutes = estimation.getMinutes();
}
```

To show the value in the same format that YouTrack uses for the issue, use `getValuePresentation()` from the field requirement.

```JAVASCRIPT
const presentation = ctx.Estimation.getValuePresentation(ctx.issue);
ctx.issue.addComment('Current estimation: ' + presentation);
```

## Convert Periods

Period objects are not numbers. When you need to compare, add, or subtract period values, convert them to a common unit first.

The following helper converts a period value to minutes. It uses a five-day work week and an eight-hour work day, which are common default time tracking settings.

```JAVASCRIPT
const WORKDAYS_PER_WEEK = 5;
const HOURS_PER_DAY = 8;

function periodToMinutes(period) {
  if (!period) {
    return 0;
  }

  return period.getMinutes() +
      60 * (period.getHours() +
          HOURS_PER_DAY * (period.getDays() +
              WORKDAYS_PER_WEEK * period.getWeeks()));
}
```

If your YouTrack instance uses different time tracking settings, adjust the constants to match your working week and working day.

After you calculate a new value in minutes, convert it back to a period before assigning it to a period field.

```JAVASCRIPT
const dateTime = require('@jetbrains/youtrack-scripting-api/date-time');

const EXTRA_MINUTES = 90;
const currentMinutes = periodToMinutes(ctx.issue.fields.Estimation);
ctx.issue.fields.Estimation = dateTime.toPeriod((currentMinutes + EXTRA_MINUTES) * 60 * 1000);
```

## Compare Period Values

To compare two period fields, convert both values to the same unit. The following rule prevents users from increasing Spent time beyond the current estimation.

```JAVASCRIPT
const entities = require('@jetbrains/youtrack-scripting-api/entities');
const workflow = require('@jetbrains/youtrack-scripting-api/workflow');

const WORKDAYS_PER_WEEK = 5;
const HOURS_PER_DAY = 8;

function periodToMinutes(period) {
  if (!period) {
    return 0;
  }

  return period.getMinutes() +
      60 * (period.getHours() +
          HOURS_PER_DAY * (period.getDays() +
              WORKDAYS_PER_WEEK * period.getWeeks()));
}

exports.rule = entities.Issue.onChange({
  title: 'Do not exceed estimation',
  guard: (ctx) => {
    return ctx.issue.fields.isChanged(ctx.SpentTime);
  },
  action: (ctx) => {
    const estimation = periodToMinutes(ctx.issue.fields.Estimation);
    const spentTime = periodToMinutes(ctx.issue.fields.SpentTime);

    workflow.check(!estimation || spentTime <= estimation,
        'Spent time cannot exceed the estimation.');
  },
  requirements: {
    Estimation: {
      type: entities.Field.periodType
    },
    SpentTime: {
      type: entities.Field.periodType,
      name: 'Spent time'
    }
  }
});
```

## Use Periods with Dates

Use `dateTime.after()` and `dateTime.before()` when a period should move a timestamp forward or backward.

```JAVASCRIPT
const dateTime = require('@jetbrains/youtrack-scripting-api/date-time');

const reminder = dateTime.before(ctx.issue.fields.DueDate, '2d');

if (ctx.issue.fields.Estimation) {
    const reviewDate = dateTime.after(Date.now(), ctx.issue.fields.Estimation);
}
```

These methods return Unix timestamps in milliseconds. Assign the result to a `date` or `date and time` field, or compare it with other timestamp values.

## Calculate Work Item Duration

Work item durations are stored as minutes. When a workflow records time between two timestamps, use `Project.intervalToWorkingMinutes()` to calculate the number of minutes that fall within the project's working hours.

```JAVASCRIPT
const duration = ctx.issue.project.intervalToWorkingMinutes(
    ctx.issue.fields.TimerTime,
    Date.now()
);

ctx.issue.addWorkItem({
  description: 'The work item automatically added by the timer.',
  date: Date.now(),
  author: ctx.currentUser,
  duration: duration
});
```

This is the same approach used by the default work timer workflows. To store a calculated duration in a period field instead, convert the number of minutes to milliseconds and pass it to `dateTime.toPeriod()`.

## Tips

* Use `dateTime.toPeriod()` before assigning values to period fields.

* Use compact period strings like `3h` and `2w4d3h15m` in workflow code.

* Use `getWeeks()`, `getDays()`, `getHours()`, and `getMinutes()` when you need to calculate with a period value.

* Convert period values to minutes before you compare them.

* Use `dateTime.after()` and `dateTime.before()` to calculate dates from periods.

* Use `Project.intervalToWorkingMinutes()` when a duration should respect project working hours.

